19b045b217a3a5877a4d3e00cb58840e23f13f7e
[controller.git] / opendaylight / forwardingrulesmanager / implementation / src / main / java / org / opendaylight / controller / forwardingrulesmanager / internal / ForwardingRulesManager.java
1 /*
2  * Copyright (c) 2013 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
9 package org.opendaylight.controller.forwardingrulesmanager.internal;
10
11 import java.io.FileNotFoundException;
12 import java.io.IOException;
13 import java.io.ObjectInputStream;
14 import java.net.InetAddress;
15 import java.net.UnknownHostException;
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.EnumSet;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Map.Entry;
23 import java.util.Set;
24 import java.util.concurrent.BlockingQueue;
25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.concurrent.ConcurrentMap;
27 import java.util.concurrent.LinkedBlockingQueue;
28
29 import org.eclipse.osgi.framework.console.CommandInterpreter;
30 import org.eclipse.osgi.framework.console.CommandProvider;
31 import org.opendaylight.controller.clustering.services.CacheConfigException;
32 import org.opendaylight.controller.clustering.services.CacheExistException;
33 import org.opendaylight.controller.clustering.services.IClusterContainerServices;
34 import org.opendaylight.controller.clustering.services.IClusterServices;
35 import org.opendaylight.controller.configuration.IConfigurationContainerAware;
36 import org.opendaylight.controller.forwardingrulesmanager.FlowConfig;
37 import org.opendaylight.controller.forwardingrulesmanager.FlowEntry;
38 import org.opendaylight.controller.forwardingrulesmanager.FlowEntryInstall;
39 import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
40 import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManagerAware;
41 import org.opendaylight.controller.forwardingrulesmanager.PortGroup;
42 import org.opendaylight.controller.forwardingrulesmanager.PortGroupChangeListener;
43 import org.opendaylight.controller.forwardingrulesmanager.PortGroupConfig;
44 import org.opendaylight.controller.forwardingrulesmanager.PortGroupProvider;
45 import org.opendaylight.controller.sal.action.Action;
46 import org.opendaylight.controller.sal.action.ActionType;
47 import org.opendaylight.controller.sal.action.Controller;
48 import org.opendaylight.controller.sal.action.Flood;
49 import org.opendaylight.controller.sal.action.Output;
50 import org.opendaylight.controller.sal.action.PopVlan;
51 import org.opendaylight.controller.sal.core.ContainerFlow;
52 import org.opendaylight.controller.sal.core.IContainer;
53 import org.opendaylight.controller.sal.core.IContainerListener;
54 import org.opendaylight.controller.sal.core.Node;
55 import org.opendaylight.controller.sal.core.NodeConnector;
56 import org.opendaylight.controller.sal.core.Property;
57 import org.opendaylight.controller.sal.core.UpdateType;
58 import org.opendaylight.controller.sal.flowprogrammer.Flow;
59 import org.opendaylight.controller.sal.flowprogrammer.IFlowProgrammerListener;
60 import org.opendaylight.controller.sal.flowprogrammer.IFlowProgrammerService;
61 import org.opendaylight.controller.sal.match.Match;
62 import org.opendaylight.controller.sal.match.MatchType;
63 import org.opendaylight.controller.sal.utils.EtherTypes;
64 import org.opendaylight.controller.sal.utils.GlobalConstants;
65 import org.opendaylight.controller.sal.utils.HexEncode;
66 import org.opendaylight.controller.sal.utils.IObjectReader;
67 import org.opendaylight.controller.sal.utils.IPProtocols;
68 import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
69 import org.opendaylight.controller.sal.utils.NodeCreator;
70 import org.opendaylight.controller.sal.utils.ObjectReader;
71 import org.opendaylight.controller.sal.utils.ObjectWriter;
72 import org.opendaylight.controller.sal.utils.Status;
73 import org.opendaylight.controller.sal.utils.StatusCode;
74 import org.opendaylight.controller.switchmanager.IInventoryListener;
75 import org.opendaylight.controller.switchmanager.ISwitchManager;
76 import org.opendaylight.controller.switchmanager.ISwitchManagerAware;
77 import org.opendaylight.controller.switchmanager.Subnet;
78 import org.osgi.framework.BundleContext;
79 import org.osgi.framework.FrameworkUtil;
80 import org.slf4j.Logger;
81 import org.slf4j.LoggerFactory;
82
83 /**
84  * Class that manages forwarding rule installation and removal per container of
85  * the network. It also maintains the central repository of all the forwarding
86  * rules installed on the network nodes.
87  */
88 public class ForwardingRulesManager implements IForwardingRulesManager, PortGroupChangeListener,
89         IContainerListener, ISwitchManagerAware, IConfigurationContainerAware, IInventoryListener, IObjectReader,
90         CommandProvider, IFlowProgrammerListener {
91     private static final String NODEDOWN = "Node is Down";
92     private static final String SUCCESS = StatusCode.SUCCESS.toString();
93     private static final Logger log = LoggerFactory.getLogger(ForwardingRulesManager.class);
94     private static final String PORTREMOVED = "Port removed";
95     private String frmFileName;
96     private String portGroupFileName;
97     private ConcurrentMap<Integer, FlowConfig> staticFlows;
98     private ConcurrentMap<Integer, Integer> staticFlowsOrdinal;
99     private ConcurrentMap<String, PortGroupConfig> portGroupConfigs;
100     private ConcurrentMap<PortGroupConfig, Map<Node, PortGroup>> portGroupData;
101     private ConcurrentMap<String, Object> TSPolicies;
102     private boolean inContainerMode; // being used by global instance only
103     private boolean stopping;
104
105     /*
106      * Flow database. It's the software view of what was requested to install
107      * and what is installed on the switch. It is indexed by the entry itself.
108      * The entry's hashcode resumes the network node index, the flow's priority
109      * and the flow's match. The value element is a class which contains the
110      * flow entry pushed by the applications modules and the respective
111      * container flow merged version. In absence of container flows, the two
112      * flow entries are the same.
113      */
114     private ConcurrentMap<FlowEntry, FlowEntry> originalSwView;
115     private ConcurrentMap<FlowEntryInstall, FlowEntryInstall> installedSwView;
116     /*
117      * Per node and per group indexing
118      */
119     private ConcurrentMap<Node, List<FlowEntryInstall>> nodeFlows;
120     private ConcurrentMap<String, List<FlowEntryInstall>> groupFlows;
121
122     /*
123      * Inactive flow list. This is for the global instance of FRM It will
124      * contain all the flow entries which were installed on the global container
125      * when the first container is created.
126      */
127     private ConcurrentMap<FlowEntry, FlowEntry> inactiveFlows;
128
129     private IContainer container;
130     private Set<IForwardingRulesManagerAware> frmAware =
131         Collections.synchronizedSet(new HashSet<IForwardingRulesManagerAware>());
132     private PortGroupProvider portGroupProvider;
133     private IFlowProgrammerService programmer;
134     private IClusterContainerServices clusterContainerService = null;
135     private ISwitchManager switchManager;
136     private Thread frmEventHandler;
137     protected BlockingQueue<FRMEvent> pendingEvents;
138
139     /**
140      * Adds a flow entry onto the network node It runs various validity checks
141      * and derive the final container flows merged entries that will be
142      * attempted to be installed
143      *
144      * @param flowEntry
145      *            the original flow entry application requested to add
146      * @param async
147      *            the flag indicating if this is a asynchronous request
148      * @return the status of this request. In case of asynchronous call, it will
149      *         contain the unique id assigned to this request
150      */
151     private Status addEntry(FlowEntry flowEntry, boolean async) {
152
153         // Sanity Check
154         if (flowEntry == null || flowEntry.getNode() == null) {
155             String msg = "Invalid FlowEntry";
156             String logMsg = msg + ": {}";
157             log.warn(logMsg, flowEntry);
158             return new Status(StatusCode.NOTACCEPTABLE, msg);
159         }
160
161         /*
162          * Derive the container flow merged entries to install In presence of N
163          * container flows, we may end up with N different entries to install...
164          */
165         List<FlowEntryInstall> toInstallList = deriveInstallEntries(flowEntry.clone(), container.getContainerFlows());
166
167         // Container Flow conflict Check
168         if (toInstallList.isEmpty()) {
169             String msg = "Flow Entry conflicts with all Container Flows";
170             String logMsg = msg + ": {}";
171             log.warn(logMsg, flowEntry);
172             return new Status(StatusCode.CONFLICT, msg);
173         }
174
175         // Derive the list of entries good to be installed
176         List<FlowEntryInstall> toInstallSafe = new ArrayList<FlowEntryInstall>();
177         for (FlowEntryInstall entry : toInstallList) {
178             // Conflict Check: Verify new entry would not overwrite existing
179             // ones
180             if (this.installedSwView.containsKey(entry)) {
181                 log.warn("Operation Rejected: A flow with same match and priority exists on the target node");
182                 log.trace("Aborting to install {}", entry);
183                 continue;
184             }
185             toInstallSafe.add(entry);
186         }
187
188         // Declare failure if all the container flow merged entries clash with
189         // existing entries
190         if (toInstallSafe.size() == 0) {
191             String msg = "A flow with same match and priority exists on the target node";
192             String logMsg = msg + ": {}";
193             log.warn(logMsg, flowEntry);
194             return new Status(StatusCode.CONFLICT, msg);
195         }
196
197         // Try to install an entry at the time
198         Status error = new Status(null, null);
199         Status succeded = null;
200         boolean oneSucceded = false;
201         for (FlowEntryInstall installEntry : toInstallSafe) {
202
203             // Install and update database
204             Status ret = addEntriesInternal(installEntry, async);
205
206             if (ret.isSuccess()) {
207                 oneSucceded = true;
208                 /*
209                  * The first successful status response will be returned For the
210                  * asynchronous call, we can discard the container flow
211                  * complication for now and assume we will always deal with one
212                  * flow only per request
213                  */
214                 succeded = ret;
215             } else {
216                 error = ret;
217                 log.warn("Failed to install the entry: {}. The failure is: {}", installEntry, ret.getDescription());
218             }
219         }
220
221         return (oneSucceded) ? succeded : error;
222     }
223
224     /**
225      * Given a flow entry and the list of container flows, it returns the list
226      * of container flow merged flow entries good to be installed on this
227      * container. If the list of container flows is null or empty, the install
228      * entry list will contain only one entry, the original flow entry. If the
229      * flow entry is congruent with all the N container flows, then the output
230      * install entry list will contain N entries. If the output list is empty,
231      * it means the passed flow entry conflicts with all the container flows.
232      *
233      * @param cFlowList
234      *            The list of container flows
235      * @return the list of container flow merged entries good to be installed on
236      *         this container
237      */
238     private List<FlowEntryInstall> deriveInstallEntries(FlowEntry request, List<ContainerFlow> cFlowList) {
239         List<FlowEntryInstall> toInstallList = new ArrayList<FlowEntryInstall>(1);
240
241         if (container.getContainerFlows() == null || container.getContainerFlows().isEmpty()) {
242             // No container flows => entry good to be installed unchanged
243             toInstallList.add(new FlowEntryInstall(request.clone(), null));
244         } else {
245             // Create the list of entries to be installed. If the flow entry is
246             // not congruent with any container flow, no install entries will be
247             // created
248             for (ContainerFlow cFlow : container.getContainerFlows()) {
249                 if (cFlow.allowsFlow(request.getFlow())) {
250                     toInstallList.add(new FlowEntryInstall(request.clone(), cFlow));
251                 }
252             }
253         }
254         return toInstallList;
255     }
256
257     /**
258      * Modify a flow entry with a new one It runs various validity check and
259      * derive the final container flows merged flow entries to work with
260      *
261      * @param currentFlowEntry
262      * @param newFlowEntry
263      * @param async
264      *            the flag indicating if this is a asynchronous request
265      * @return the status of this request. In case of asynchronous call, it will
266      *         contain the unique id assigned to this request
267      */
268     private Status modifyEntry(FlowEntry currentFlowEntry, FlowEntry newFlowEntry, boolean async) {
269         Status retExt;
270
271         // Sanity checks
272         if (currentFlowEntry == null || currentFlowEntry.getNode() == null || newFlowEntry == null
273                 || newFlowEntry.getNode() == null) {
274             String msg = "Modify: Invalid FlowEntry";
275             String logMsg = msg + ": {} or {}";
276             log.warn(logMsg, currentFlowEntry, newFlowEntry);
277             return new Status(StatusCode.NOTACCEPTABLE, msg);
278         }
279         if (!currentFlowEntry.getNode().equals(newFlowEntry.getNode())
280                 || !currentFlowEntry.getFlowName().equals(newFlowEntry.getFlowName())) {
281             String msg = "Modify: Incompatible Flow Entries";
282             String logMsg = msg + ": {} and {}";
283             log.warn(logMsg, currentFlowEntry, newFlowEntry);
284             return new Status(StatusCode.NOTACCEPTABLE, msg);
285         }
286
287         // Equality Check
288         if (currentFlowEntry.getFlow().equals(newFlowEntry.getFlow())) {
289             String msg = "Modify skipped as flows are the same";
290             String logMsg = msg + ": {} and {}";
291             log.debug(logMsg, currentFlowEntry, newFlowEntry);
292             return new Status(StatusCode.SUCCESS, msg);
293         }
294
295         /*
296          * Conflict Check: Verify the new entry would not conflict with an
297          * existing one. This is a loose check on the previous original flow
298          * entry requests. No check on the container flow merged flow entries
299          * (if any) yet
300          */
301         FlowEntry sameMatchOriginalEntry = originalSwView.get(newFlowEntry);
302         if (sameMatchOriginalEntry != null && !sameMatchOriginalEntry.equals(currentFlowEntry)) {
303             String msg = "Operation Rejected: Another flow with same match and priority exists on the target node";
304             String logMsg = msg + ": {}";
305             log.warn(logMsg, currentFlowEntry);
306             return new Status(StatusCode.CONFLICT, msg);
307         }
308
309         // Derive the installed and toInstall entries
310         List<FlowEntryInstall> installedList = deriveInstallEntries(currentFlowEntry.clone(),
311                 container.getContainerFlows());
312         List<FlowEntryInstall> toInstallList = deriveInstallEntries(newFlowEntry.clone(), container.getContainerFlows());
313
314         if (toInstallList.isEmpty()) {
315             String msg = "Modify Operation Rejected: The new entry conflicts with all the container flows";
316             String logMsg = msg + ": {}";
317             log.warn(logMsg, newFlowEntry);
318             log.warn(msg);
319             return new Status(StatusCode.CONFLICT, msg);
320         }
321
322         /*
323          * If the two list sizes differ, it means the new flow entry does not
324          * satisfy the same number of container flows the current entry does.
325          * This is only possible when the new entry and current entry have
326          * different match. In this scenario the modification would ultimately
327          * be handled as a remove and add operations in the protocol plugin.
328          *
329          * Also, if any of the new flow entries would clash with an existing
330          * one, we cannot proceed with the modify operation, because it would
331          * fail for some entries and leave stale entries on the network node.
332          * Modify path can be taken only if it can be performed completely, for
333          * all entries.
334          *
335          * So, for the above two cases, to simplify, let's decouple the modify
336          * in: 1) remove current entries 2) install new entries
337          */
338         Status succeeded = null;
339         boolean decouple = false;
340         if (installedList.size() != toInstallList.size()) {
341             log.info("Modify: New flow entry does not satisfy the same "
342                     + "number of container flows as the original entry does");
343             decouple = true;
344         }
345         List<FlowEntryInstall> toInstallSafe = new ArrayList<FlowEntryInstall>();
346         for (FlowEntryInstall installEntry : toInstallList) {
347             /*
348              * Conflict Check: Verify the new entry would not overwrite another
349              * existing one
350              */
351             FlowEntryInstall sameMatchEntry = installedSwView.get(installEntry);
352             if (sameMatchEntry != null && !sameMatchEntry.getOriginal().equals(currentFlowEntry)) {
353                 log.info("Modify: new container flow merged flow entry clashes with existing flow");
354                 decouple = true;
355             } else {
356                 toInstallSafe.add(installEntry);
357             }
358         }
359
360         if (decouple) {
361             // Remove current entries
362             for (FlowEntryInstall currEntry : installedList) {
363                 this.removeEntryInternal(currEntry, async);
364             }
365             // Install new entries
366             for (FlowEntryInstall newEntry : toInstallSafe) {
367                 succeeded = this.addEntriesInternal(newEntry, async);
368             }
369         } else {
370             /*
371              * The two list have the same size and the entries to install do not
372              * clash with any existing flow on the network node. We assume here
373              * (and might be wrong) that the same container flows that were
374              * satisfied by the current entries are the same that are satisfied
375              * by the new entries. Let's take the risk for now.
376              *
377              * Note: modification has to be complete. If any entry modification
378              * fails, we need to stop, restore the already modified entries, and
379              * declare failure.
380              */
381             Status retModify = null;
382             int i = 0;
383             int size = toInstallList.size();
384             while (i < size) {
385                 // Modify and update database
386                 retModify = modifyEntryInternal(installedList.get(i), toInstallList.get(i), async);
387                 if (retModify.isSuccess()) {
388                     i++;
389                 } else {
390                     break;
391                 }
392             }
393             // Check if uncompleted modify
394             if (i < size) {
395                 log.warn("Unable to perform a complete modify for all  the container flows merged entries");
396                 // Restore original entries
397                 int j = 0;
398                 while (j < i) {
399                     log.info("Attempting to restore initial entries");
400                     retExt = modifyEntryInternal(toInstallList.get(i), installedList.get(i), async);
401                     if (retExt.isSuccess()) {
402                         j++;
403                     } else {
404                         break;
405                     }
406                 }
407                 // Fatal error, recovery failed
408                 if (j < i) {
409                     String msg = "Flow recovery failed ! Unrecoverable Error";
410                     log.error(msg);
411                     return new Status(StatusCode.INTERNALERROR, msg);
412                 }
413             }
414             succeeded = retModify;
415         }
416         /*
417          * The first successful status response will be returned. For the
418          * asynchronous call, we can discard the container flow complication for
419          * now and assume we will always deal with one flow only per request
420          */
421         return succeeded;
422     }
423
424     /**
425      * This is the function that modifies the final container flows merged
426      * entries on the network node and update the database. It expects that all
427      * the validity checks are passed
428      *
429      * @param currentEntries
430      * @param newEntries
431      * @param async
432      *            the flag indicating if this is a asynchronous request
433      * @return the status of this request. In case of asynchronous call, it will
434      *         contain the unique id assigned to this request
435      */
436     private Status modifyEntryInternal(FlowEntryInstall currentEntries, FlowEntryInstall newEntries, boolean async) {
437         // Modify the flow on the network node
438         Status status = (async) ? programmer.modifyFlowAsync(currentEntries.getNode(), currentEntries.getInstall()
439                 .getFlow(), newEntries.getInstall().getFlow()) : programmer.modifyFlow(currentEntries.getNode(),
440                 currentEntries.getInstall().getFlow(), newEntries.getInstall().getFlow());
441
442         if (!status.isSuccess()) {
443             log.warn("SDN Plugin failed to program the flow: {}. The failure is: {}", newEntries.getInstall(),
444                     status.getDescription());
445             return status;
446         }
447
448         log.trace("Modified {} => {}", currentEntries.getInstall(), newEntries.getInstall());
449
450         // Update DB
451         newEntries.setRequestId(status.getRequestId());
452         updateLocalDatabase(currentEntries, false);
453         updateLocalDatabase(newEntries, true);
454
455         return status;
456     }
457
458     /**
459      * Remove a flow entry. If the entry is not present in the software view
460      * (entry or node not present), it return successfully
461      *
462      * @param flowEntry
463      *            the flow entry to remove
464      * @param async
465      *            the flag indicating if this is a asynchronous request
466      * @return the status of this request. In case of asynchronous call, it will
467      *         contain the unique id assigned to this request
468      */
469     private Status removeEntry(FlowEntry flowEntry, boolean async) {
470         Status error = new Status(null, null);
471
472         // Sanity Check
473         if (flowEntry == null || flowEntry.getNode() == null) {
474             String msg = "Invalid FlowEntry";
475             String logMsg = msg + ": {}";
476             log.warn(logMsg, flowEntry);
477             return new Status(StatusCode.NOTACCEPTABLE, msg);
478         }
479
480         // Derive the container flows merged installed entries
481         List<FlowEntryInstall> installedList = deriveInstallEntries(flowEntry.clone(), container.getContainerFlows());
482
483         Status succeeded = null;
484         boolean atLeastOneRemoved = false;
485         for (FlowEntryInstall entry : installedList) {
486             if (!installedSwView.containsKey(entry)) {
487                 String logMsg = "Removal skipped (not present in software view) for flow entry: {}";
488                 log.debug(logMsg, flowEntry);
489                 if (installedList.size() == 1) {
490                     // If we had only one entry to remove, we are done
491                     return new Status(StatusCode.SUCCESS);
492                 } else {
493                     continue;
494                 }
495             }
496
497             // Remove and update DB
498             Status ret = removeEntryInternal(entry, async);
499
500             if (!ret.isSuccess()) {
501                 error = ret;
502                 log.warn("Failed to remove the entry: {}. The failure is: {}", entry.getInstall(), ret.getDescription());
503                 if (installedList.size() == 1) {
504                     // If we had only one entry to remove, this is fatal failure
505                     return error;
506                 }
507             } else {
508                 succeeded = ret;
509                 atLeastOneRemoved = true;
510             }
511         }
512
513         /*
514          * No worries if full removal failed. Consistency checker will take care
515          * of removing the stale entries later, or adjusting the software
516          * database if not in sync with hardware
517          */
518         return (atLeastOneRemoved) ? succeeded : error;
519     }
520
521     /**
522      * This is the function that removes the final container flows merged entry
523      * from the network node and update the database. It expects that all the
524      * validity checks are passed
525      *
526      * @param entry
527      *            the flow entry to remove
528      * @param async
529      *            the flag indicating if this is a asynchronous request
530      * @return the status of this request. In case of asynchronous call, it will
531      *         contain the unique id assigned to this request
532      */
533     private Status removeEntryInternal(FlowEntryInstall entry, boolean async) {
534         // Mark the entry to be deleted (for CC just in case we fail)
535         entry.toBeDeleted();
536
537         // Remove from node
538         Status status = (async) ? programmer.removeFlowAsync(entry.getNode(), entry.getInstall().getFlow())
539                 : programmer.removeFlow(entry.getNode(), entry.getInstall().getFlow());
540
541         if (!status.isSuccess()) {
542             log.warn("SDN Plugin failed to program the flow: {}. The failure is: {}", entry.getInstall(),
543                     status.getDescription());
544             return status;
545         }
546         log.trace("Removed  {}", entry.getInstall());
547
548         // Update DB
549         updateLocalDatabase(entry, false);
550
551         return status;
552     }
553
554     /**
555      * This is the function that installs the final container flow merged entry
556      * on the network node and updates the database. It expects that all the
557      * validity and conflict checks are passed. That means it does not check
558      * whether this flow would conflict or overwrite an existing one.
559      *
560      * @param entry
561      *            the flow entry to install
562      * @param async
563      *            the flag indicating if this is a asynchronous request
564      * @return the status of this request. In case of asynchronous call, it will
565      *         contain the unique id assigned to this request
566      */
567     private Status addEntriesInternal(FlowEntryInstall entry, boolean async) {
568         // Install the flow on the network node
569         Status status = (async) ? programmer.addFlowAsync(entry.getNode(), entry.getInstall().getFlow()) : programmer
570                 .addFlow(entry.getNode(), entry.getInstall().getFlow());
571
572         if (!status.isSuccess()) {
573             log.warn("SDN Plugin failed to program the flow: {}. The failure is: {}", entry.getInstall(),
574                     status.getDescription());
575             return status;
576         }
577
578         log.trace("Added    {}", entry.getInstall());
579
580         // Update DB
581         entry.setRequestId(status.getRequestId());
582         updateLocalDatabase(entry, true);
583
584         return status;
585     }
586
587     /**
588      * Returns true if the flow conflicts with all the container's flows. This
589      * means that if the function returns true, the passed flow entry is
590      * congruent with at least one container flow, hence it is good to be
591      * installed on this container.
592      *
593      * @param flowEntry
594      * @return true if flow conflicts with all the container flows, false
595      *         otherwise
596      */
597     private boolean entryConflictsWithContainerFlows(FlowEntry flowEntry) {
598         List<ContainerFlow> cFlowList = container.getContainerFlows();
599
600         // Validity check and avoid unnecessary computation
601         // Also takes care of default container where no container flows are
602         // present
603         if (cFlowList == null || cFlowList.isEmpty()) {
604             return false;
605         }
606
607         for (ContainerFlow cFlow : cFlowList) {
608             if (cFlow.allowsFlow(flowEntry.getFlow())) {
609                 // Entry is allowed by at least one container flow: good to go
610                 return false;
611             }
612         }
613         return true;
614     }
615
616     private void updateLocalDatabase(FlowEntryInstall entry, boolean add) {
617         // Update the software view
618         updateSwViewes(entry, add);
619
620         // Update node indexed flow database
621         updateNodeFlowsDB(entry, add);
622
623         // Update group indexed flow database
624         updateGroupFlowsDB(entry, add);
625     }
626
627     /*
628      * Update the node mapped flows database
629      */
630     private void updateSwViewes(FlowEntryInstall flowEntries, boolean add) {
631         if (add) {
632             originalSwView.put(flowEntries.getOriginal(), flowEntries.getOriginal());
633             installedSwView.put(flowEntries, flowEntries);
634         } else {
635             originalSwView.remove(flowEntries.getOriginal());
636             installedSwView.remove(flowEntries);
637         }
638     }
639
640     /*
641      * Update the node mapped flows database
642      */
643     private void updateNodeFlowsDB(FlowEntryInstall flowEntries, boolean add) {
644         Node node = flowEntries.getNode();
645
646         List<FlowEntryInstall> nodeIndeces = this.nodeFlows.get(node);
647         if (nodeIndeces == null) {
648             if (!add) {
649                 return;
650             } else {
651                 nodeIndeces = new ArrayList<FlowEntryInstall>();
652             }
653         }
654
655         if (add) {
656             nodeIndeces.add(flowEntries);
657         } else {
658             nodeIndeces.remove(flowEntries);
659         }
660
661         // Update cache across cluster
662         if (nodeIndeces.isEmpty()) {
663             this.nodeFlows.remove(node);
664         } else {
665             this.nodeFlows.put(node, nodeIndeces);
666         }
667     }
668
669     /*
670      * Update the group name mapped flows database
671      */
672     private void updateGroupFlowsDB(FlowEntryInstall flowEntries, boolean add) {
673         String groupName = flowEntries.getGroupName();
674
675         // Flow may not be part of a group
676         if (groupName == null) {
677             return;
678         }
679
680         List<FlowEntryInstall> indices = this.groupFlows.get(groupName);
681         if (indices == null) {
682             if (!add) {
683                 return;
684             } else {
685                 indices = new ArrayList<FlowEntryInstall>();
686             }
687         }
688
689         if (add) {
690             indices.add(flowEntries);
691         } else {
692             indices.remove(flowEntries);
693         }
694
695         // Update cache across cluster
696         if (indices.isEmpty()) {
697             this.groupFlows.remove(groupName);
698         } else {
699             this.groupFlows.put(groupName, indices);
700         }
701     }
702
703     /**
704      * Remove a flow entry that has been added previously First checks if the
705      * entry is effectively present in the local database
706      */
707     @SuppressWarnings("unused")
708     private Status removeEntry(Node node, String flowName) {
709         FlowEntryInstall target = null;
710
711         // Find in database
712         for (FlowEntryInstall entry : installedSwView.values()) {
713             if (entry.equalsByNodeAndName(node, flowName)) {
714                 target = entry;
715                 break;
716             }
717         }
718
719         // If it is not there, stop any further processing
720         if (target == null) {
721             return new Status(StatusCode.SUCCESS, "Entry is not present");
722         }
723
724         // Remove from node
725         Status status = programmer.removeFlow(target.getNode(), target.getInstall().getFlow());
726
727         // Update DB
728         if (status.isSuccess()) {
729             updateLocalDatabase(target, false);
730         } else {
731             // log the error
732             log.warn("SDN Plugin failed to remove the flow: {}. The failure is: {}", target.getInstall(),
733                     status.getDescription());
734         }
735
736         return status;
737     }
738
739     @Override
740     public Status installFlowEntry(FlowEntry flowEntry) {
741         Status status;
742         if (isContainerModeAllowed(flowEntry)) {
743             status = addEntry(flowEntry, false);
744         } else {
745             String msg = "Controller in container mode: Install Refused";
746             String logMsg = msg + ": {}";
747             status = new Status(StatusCode.NOTACCEPTABLE, msg);
748             log.warn(logMsg, flowEntry);
749         }
750         return status;
751     }
752
753     @Override
754     public Status installFlowEntryAsync(FlowEntry flowEntry) {
755         Status status;
756         if (isContainerModeAllowed(flowEntry)) {
757             status = addEntry(flowEntry, true);
758         } else {
759             String msg = "Controller in container mode: Install Refused";
760             status = new Status(StatusCode.NOTACCEPTABLE, msg);
761             log.warn(msg);
762         }
763         return status;
764     }
765
766     @Override
767     public Status uninstallFlowEntry(FlowEntry flowEntry) {
768         Status status;
769         if (isContainerModeAllowed(flowEntry)) {
770             status = removeEntry(flowEntry, false);
771         } else {
772             String msg = "Controller in container mode: Uninstall Refused";
773             String logMsg = msg + ": {}";
774             status = new Status(StatusCode.NOTACCEPTABLE, msg);
775             log.warn(logMsg, flowEntry);
776         }
777         return status;
778     }
779
780     @Override
781     public Status uninstallFlowEntryAsync(FlowEntry flowEntry) {
782         Status status;
783         if (isContainerModeAllowed(flowEntry)) {
784             status = removeEntry(flowEntry, true);
785         } else {
786             String msg = "Controller in container mode: Uninstall Refused";
787             status = new Status(StatusCode.NOTACCEPTABLE, msg);
788             log.warn(msg);
789         }
790         return status;
791     }
792
793     @Override
794     public Status modifyFlowEntry(FlowEntry currentFlowEntry, FlowEntry newFlowEntry) {
795         Status status = null;
796         if (isContainerModeAllowed(currentFlowEntry)) {
797             status = modifyEntry(currentFlowEntry, newFlowEntry, false);
798         } else {
799             String msg = "Controller in container mode: Modify Refused";
800             String logMsg = msg + ": {}";
801             status = new Status(StatusCode.NOTACCEPTABLE, msg);
802             log.warn(logMsg, newFlowEntry);
803         }
804         return status;
805     }
806
807     @Override
808     public Status modifyFlowEntryAsync(FlowEntry currentFlowEntry, FlowEntry newFlowEntry) {
809         Status status = null;
810         if (isContainerModeAllowed(currentFlowEntry)) {
811             status = modifyEntry(currentFlowEntry, newFlowEntry, true);
812         } else {
813             String msg = "Controller in container mode: Modify Refused";
814             status = new Status(StatusCode.NOTACCEPTABLE, msg);
815             log.warn(msg);
816         }
817         return status;
818     }
819
820     /**
821      * Returns whether the specified flow entry is allowed to be
822      * installed/removed/modified based on the current container mode status.
823      * This call always returns true in the container instance of forwarding
824      * rules manager. It is meant for the global instance only (default
825      * container) of forwarding rules manager. Idea is that for assuring
826      * container isolation of traffic, flow installation in default container is
827      * blocked when in container mode (containers are present). The only flows
828      * that are allowed in container mode in the default container are the
829      * proactive flows, the ones automatically installed on the network node
830      * which forwarding mode has been configured to "proactive". These flows are
831      * needed by controller to discover the nodes topology and to discover the
832      * attached hosts for some SDN switches.
833      *
834      * @param flowEntry
835      *            The flow entry to be installed/removed/modified
836      * @return true if not in container mode or if flowEntry is internally
837      *         generated
838      */
839     private boolean isContainerModeAllowed(FlowEntry flowEntry) {
840         return (!inContainerMode) ? true : flowEntry.isInternal();
841     }
842
843     @Override
844     public Status modifyOrAddFlowEntry(FlowEntry newFlowEntry) {
845         /*
846          * Run a check on the original entries to decide whether to go with a
847          * add or modify method. A loose check means only check against the
848          * original flow entry requests and not against the installed flow
849          * entries which are the result of the original entry merged with the
850          * container flow(s) (if any). The modifyFlowEntry method in presence of
851          * conflicts with the Container flows (if any) would revert back to a
852          * delete + add pattern
853          */
854         FlowEntry currentFlowEntry = originalSwView.get(newFlowEntry);
855
856         if (currentFlowEntry != null) {
857             return modifyFlowEntry(currentFlowEntry, newFlowEntry);
858         } else {
859             return installFlowEntry(newFlowEntry);
860         }
861     }
862
863     @Override
864     public Status modifyOrAddFlowEntryAsync(FlowEntry newFlowEntry) {
865         /*
866          * Run a check on the original entries to decide whether to go with a
867          * add or modify method. A loose check means only check against the
868          * original flow entry requests and not against the installed flow
869          * entries which are the result of the original entry merged with the
870          * container flow(s) (if any). The modifyFlowEntry method in presence of
871          * conflicts with the Container flows (if any) would revert back to a
872          * delete + add pattern
873          */
874         FlowEntry currentFlowEntry = originalSwView.get(newFlowEntry);
875
876         if (currentFlowEntry != null) {
877             return modifyFlowEntryAsync(currentFlowEntry, newFlowEntry);
878         } else {
879             return installFlowEntryAsync(newFlowEntry);
880         }
881     }
882
883     @Override
884     public Status uninstallFlowEntryGroup(String groupName) {
885         if (groupName == null || groupName.isEmpty()) {
886             return new Status(StatusCode.BADREQUEST, "Invalid group name");
887         }
888         if (groupName.equals(FlowConfig.INTERNALSTATICFLOWGROUP)) {
889             return new Status(StatusCode.BADREQUEST, "Internal static flows group cannot be deleted through this api");
890         }
891         if (inContainerMode) {
892             String msg = "Controller in container mode: Group Uninstall Refused";
893             String logMsg = msg + ": {}";
894             log.warn(logMsg, groupName);
895             return new Status(StatusCode.NOTACCEPTABLE, msg);
896         }
897         int toBeRemoved = 0;
898         String error = "";
899         if (groupFlows.containsKey(groupName)) {
900             List<FlowEntryInstall> list = new ArrayList<FlowEntryInstall>(groupFlows.get(groupName));
901             toBeRemoved = list.size();
902             for (FlowEntryInstall entry : list) {
903                 Status status = this.removeEntry(entry.getOriginal(), false);
904                 if (status.isSuccess()) {
905                     toBeRemoved -= 1;
906                 } else {
907                     error = status.getDescription();
908                 }
909             }
910         }
911         return (toBeRemoved == 0) ? new Status(StatusCode.SUCCESS) : new Status(StatusCode.INTERNALERROR,
912                 "Not all the flows were removed: " + error);
913     }
914
915     @Override
916     public Status uninstallFlowEntryGroupAsync(String groupName) {
917         if (groupName == null || groupName.isEmpty()) {
918             return new Status(StatusCode.BADREQUEST, "Invalid group name");
919         }
920         if (groupName.equals(FlowConfig.INTERNALSTATICFLOWGROUP)) {
921             return new Status(StatusCode.BADREQUEST, "Static flows group cannot be deleted through this api");
922         }
923         if (inContainerMode) {
924             String msg = "Controller in container mode: Group Uninstall Refused";
925             String logMsg = msg + ": {}";
926             log.warn(logMsg, groupName);
927             return new Status(StatusCode.NOTACCEPTABLE, msg);
928         }
929         if (groupFlows.containsKey(groupName)) {
930             List<FlowEntryInstall> list = new ArrayList<FlowEntryInstall>(groupFlows.get(groupName));
931             for (FlowEntryInstall entry : list) {
932                 this.removeEntry(entry.getOriginal(), true);
933             }
934         }
935         return new Status(StatusCode.SUCCESS);
936     }
937
938     @Override
939     public boolean checkFlowEntryConflict(FlowEntry flowEntry) {
940         return entryConflictsWithContainerFlows(flowEntry);
941     }
942
943     /**
944      * Updates all installed flows because the container flow got updated This
945      * is obtained in two phases on per node basis: 1) Uninstall of all flows 2)
946      * Reinstall of all flows This is needed because a new container flows
947      * merged flow may conflict with an existing old container flows merged flow
948      * on the network node
949      */
950     private void updateFlowsContainerFlow() {
951         Set<FlowEntry> toReInstall = new HashSet<FlowEntry>();
952         // First remove all installed entries
953         for (ConcurrentMap.Entry<FlowEntryInstall, FlowEntryInstall> entry : installedSwView.entrySet()) {
954             FlowEntryInstall current = entry.getValue();
955             // Store the original entry
956             toReInstall.add(current.getOriginal());
957             // Remove the old couples. No validity checks to be run, use the
958             // internal remove
959             this.removeEntryInternal(current, false);
960         }
961         // Then reinstall the original entries
962         for (FlowEntry entry : toReInstall) {
963             // Reinstall the original flow entries, via the regular path: new
964             // cFlow merge + validations
965             this.installFlowEntry(entry);
966         }
967     }
968
969     private void nonClusterObjectCreate() {
970         originalSwView = new ConcurrentHashMap<FlowEntry, FlowEntry>();
971         installedSwView = new ConcurrentHashMap<FlowEntryInstall, FlowEntryInstall>();
972         nodeFlows = new ConcurrentHashMap<Node, List<FlowEntryInstall>>();
973         groupFlows = new ConcurrentHashMap<String, List<FlowEntryInstall>>();
974         TSPolicies = new ConcurrentHashMap<String, Object>();
975         staticFlowsOrdinal = new ConcurrentHashMap<Integer, Integer>();
976         portGroupConfigs = new ConcurrentHashMap<String, PortGroupConfig>();
977         portGroupData = new ConcurrentHashMap<PortGroupConfig, Map<Node, PortGroup>>();
978         staticFlows = new ConcurrentHashMap<Integer, FlowConfig>();
979         inactiveFlows = new ConcurrentHashMap<FlowEntry, FlowEntry>();
980     }
981
982     private void registerWithOSGIConsole() {
983         BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
984         bundleContext.registerService(CommandProvider.class.getName(), this, null);
985     }
986
987     @Override
988     public void setTSPolicyData(String policyname, Object o, boolean add) {
989
990         if (add) {
991             /* Check if this policy already exists */
992             if (!(TSPolicies.containsKey(policyname))) {
993                 TSPolicies.put(policyname, o);
994             }
995         } else {
996             TSPolicies.remove(policyname);
997         }
998         if (frmAware != null) {
999             synchronized (frmAware) {
1000                 for (IForwardingRulesManagerAware frma : frmAware) {
1001                     try {
1002                         frma.policyUpdate(policyname, add);
1003                     } catch (Exception e) {
1004                         log.warn("Exception on callback", e);
1005                     }
1006                 }
1007             }
1008         }
1009     }
1010
1011     @Override
1012     public Map<String, Object> getTSPolicyData() {
1013         return TSPolicies;
1014     }
1015
1016     @Override
1017     public Object getTSPolicyData(String policyName) {
1018         if (TSPolicies.containsKey(policyName)) {
1019             return TSPolicies.get(policyName);
1020         } else {
1021             return null;
1022         }
1023     }
1024
1025     @Override
1026     public List<FlowEntry> getFlowEntriesForGroup(String policyName) {
1027         List<FlowEntry> list = new ArrayList<FlowEntry>();
1028         if (policyName != null && !policyName.trim().isEmpty()) {
1029             for (Map.Entry<FlowEntry, FlowEntry> entry : this.originalSwView.entrySet()) {
1030                 if (policyName.equals(entry.getKey().getGroupName())) {
1031                     list.add(entry.getKey().clone());
1032                 }
1033             }
1034         }
1035         return list;
1036     }
1037
1038     @Override
1039     public List<FlowEntry> getInstalledFlowEntriesForGroup(String policyName) {
1040         List<FlowEntry> list = new ArrayList<FlowEntry>();
1041         if (policyName != null && !policyName.trim().isEmpty()) {
1042             for (Map.Entry<FlowEntryInstall, FlowEntryInstall> entry : this.installedSwView.entrySet()) {
1043                 if (policyName.equals(entry.getKey().getGroupName())) {
1044                     list.add(entry.getKey().getInstall().clone());
1045                 }
1046             }
1047         }
1048         return list;
1049     }
1050
1051     @Override
1052     public void addOutputPort(Node node, String flowName, List<NodeConnector> portList) {
1053
1054         for (FlowEntryInstall flow : this.nodeFlows.get(node)) {
1055             if (flow.getFlowName().equals(flowName)) {
1056                 FlowEntry currentFlowEntry = flow.getOriginal();
1057                 FlowEntry newFlowEntry = currentFlowEntry.clone();
1058                 for (NodeConnector dstPort : portList) {
1059                     newFlowEntry.getFlow().addAction(new Output(dstPort));
1060                 }
1061                 Status error = modifyEntry(currentFlowEntry, newFlowEntry, false);
1062                 if (error.isSuccess()) {
1063                     log.info("Ports {} added to FlowEntry {}", portList, flowName);
1064                 } else {
1065                     log.warn("Failed to add ports {} to Flow entry {}. The failure is: {}", portList,
1066                             currentFlowEntry.toString(), error.getDescription());
1067                 }
1068                 return;
1069             }
1070         }
1071         log.warn("Failed to add ports to Flow {} on Node {}: Entry Not Found", flowName, node);
1072     }
1073
1074     @Override
1075     public void removeOutputPort(Node node, String flowName, List<NodeConnector> portList) {
1076         for (FlowEntryInstall index : this.nodeFlows.get(node)) {
1077             FlowEntryInstall flow = this.installedSwView.get(index);
1078             if (flow.getFlowName().equals(flowName)) {
1079                 FlowEntry currentFlowEntry = flow.getOriginal();
1080                 FlowEntry newFlowEntry = currentFlowEntry.clone();
1081                 for (NodeConnector dstPort : portList) {
1082                     Action action = new Output(dstPort);
1083                     newFlowEntry.getFlow().removeAction(action);
1084                 }
1085                 Status status = modifyEntry(currentFlowEntry, newFlowEntry, false);
1086                 if (status.isSuccess()) {
1087                     log.info("Ports {} removed from FlowEntry {}", portList, flowName);
1088                 } else {
1089                     log.warn("Failed to remove ports {} from Flow entry {}. The failure is: {}", portList,
1090                             currentFlowEntry.toString(), status.getDescription());
1091                 }
1092                 return;
1093             }
1094         }
1095         log.warn("Failed to remove ports from Flow {} on Node {}: Entry Not Found", flowName, node);
1096     }
1097
1098     /*
1099      * This function assumes the target flow has only one output port
1100      */
1101     @Override
1102     public void replaceOutputPort(Node node, String flowName, NodeConnector outPort) {
1103         FlowEntry currentFlowEntry = null;
1104         FlowEntry newFlowEntry = null;
1105
1106         // Find the flow
1107         for (FlowEntryInstall index : this.nodeFlows.get(node)) {
1108             FlowEntryInstall flow = this.installedSwView.get(index);
1109             if (flow.getFlowName().equals(flowName)) {
1110                 currentFlowEntry = flow.getOriginal();
1111                 break;
1112             }
1113         }
1114         if (currentFlowEntry == null) {
1115             log.warn("Failed to replace output port for flow {} on node {}: Entry Not Found", flowName, node);
1116             return;
1117         }
1118
1119         // Create a flow copy with the new output port
1120         newFlowEntry = currentFlowEntry.clone();
1121         Action target = null;
1122         for (Action action : newFlowEntry.getFlow().getActions()) {
1123             if (action.getType() == ActionType.OUTPUT) {
1124                 target = action;
1125                 break;
1126             }
1127         }
1128         newFlowEntry.getFlow().removeAction(target);
1129         newFlowEntry.getFlow().addAction(new Output(outPort));
1130
1131         // Modify on network node
1132         Status status = modifyEntry(currentFlowEntry, newFlowEntry, false);
1133
1134         if (status.isSuccess()) {
1135             log.info("Output port replaced with {} for flow {} on node {}", outPort, flowName, node);
1136         } else {
1137             log.warn("Failed to replace output port for flow {} on node {}. The failure is: {}", flowName, node,
1138                     status.getDescription());
1139         }
1140         return;
1141     }
1142
1143     @Override
1144     public NodeConnector getOutputPort(Node node, String flowName) {
1145         for (FlowEntryInstall index : this.nodeFlows.get(node)) {
1146             FlowEntryInstall flow = this.installedSwView.get(index);
1147             if (flow.getFlowName().equals(flowName)) {
1148                 for (Action action : flow.getOriginal().getFlow().getActions()) {
1149                     if (action.getType() == ActionType.OUTPUT) {
1150                         return ((Output) action).getPort();
1151                     }
1152                 }
1153             }
1154         }
1155         return null;
1156     }
1157
1158     private void cacheStartup() {
1159         allocateCaches();
1160         retrieveCaches();
1161     }
1162
1163     @SuppressWarnings("deprecation")
1164     private void allocateCaches() {
1165         if (this.clusterContainerService == null) {
1166             log.warn("Un-initialized clusterContainerService, can't create cache");
1167             return;
1168         }
1169
1170         log.debug("Allocating caches for Container {}", container.getName());
1171
1172         try {
1173             clusterContainerService.createCache("frm.originalSwView",
1174                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
1175
1176             clusterContainerService.createCache("frm.installedSwView",
1177                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
1178
1179             clusterContainerService.createCache("frm.inactiveFlows",
1180                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
1181
1182             clusterContainerService.createCache("frm.nodeFlows",
1183                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
1184
1185             clusterContainerService.createCache("frm.groupFlows",
1186                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
1187
1188             clusterContainerService.createCache("frm.staticFlows",
1189                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
1190
1191             clusterContainerService.createCache("frm.flowsSaveEvent",
1192                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
1193
1194             clusterContainerService.createCache("frm.staticFlowsOrdinal",
1195                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
1196
1197             clusterContainerService.createCache("frm.portGroupConfigs",
1198                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
1199
1200             clusterContainerService.createCache("frm.portGroupData",
1201                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
1202
1203             clusterContainerService.createCache("frm.TSPolicies",
1204                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
1205
1206         } catch (CacheConfigException cce) {
1207             log.error("CacheConfigException");
1208         } catch (CacheExistException cce) {
1209             log.error("CacheExistException");
1210         }
1211     }
1212
1213     @SuppressWarnings({ "unchecked", "deprecation" })
1214     private void retrieveCaches() {
1215         ConcurrentMap<?, ?> map;
1216
1217         if (this.clusterContainerService == null) {
1218             log.warn("un-initialized clusterContainerService, can't retrieve cache");
1219             nonClusterObjectCreate();
1220             return;
1221         }
1222
1223         log.debug("Retrieving Caches for Container {}", container.getName());
1224
1225         map = clusterContainerService.getCache("frm.originalSwView");
1226         if (map != null) {
1227             originalSwView = (ConcurrentMap<FlowEntry, FlowEntry>) map;
1228         } else {
1229             log.error("Retrieval of frm.originalSwView cache failed for Container {}", container.getName());
1230         }
1231
1232         map = clusterContainerService.getCache("frm.installedSwView");
1233         if (map != null) {
1234             installedSwView = (ConcurrentMap<FlowEntryInstall, FlowEntryInstall>) map;
1235         } else {
1236             log.error("Retrieval of frm.installedSwView cache failed for Container {}", container.getName());
1237         }
1238
1239         map = clusterContainerService.getCache("frm.inactiveFlows");
1240         if (map != null) {
1241             inactiveFlows = (ConcurrentMap<FlowEntry, FlowEntry>) map;
1242         } else {
1243             log.error("Retrieval of frm.inactiveFlows cache failed for Container {}", container.getName());
1244         }
1245
1246         map = clusterContainerService.getCache("frm.nodeFlows");
1247         if (map != null) {
1248             nodeFlows = (ConcurrentMap<Node, List<FlowEntryInstall>>) map;
1249         } else {
1250             log.error("Retrieval of cache failed for Container {}", container.getName());
1251         }
1252
1253         map = clusterContainerService.getCache("frm.groupFlows");
1254         if (map != null) {
1255             groupFlows = (ConcurrentMap<String, List<FlowEntryInstall>>) map;
1256         } else {
1257             log.error("Retrieval of frm.groupFlows cache failed for Container {}", container.getName());
1258         }
1259
1260         map = clusterContainerService.getCache("frm.staticFlows");
1261         if (map != null) {
1262             staticFlows = (ConcurrentMap<Integer, FlowConfig>) map;
1263         } else {
1264             log.error("Retrieval of frm.staticFlows cache failed for Container {}", container.getName());
1265         }
1266
1267         map = clusterContainerService.getCache("frm.staticFlowsOrdinal");
1268         if (map != null) {
1269             staticFlowsOrdinal = (ConcurrentMap<Integer, Integer>) map;
1270         } else {
1271             log.error("Retrieval of frm.staticFlowsOrdinal cache failed for Container {}", container.getName());
1272         }
1273
1274         map = clusterContainerService.getCache("frm.portGroupConfigs");
1275         if (map != null) {
1276             portGroupConfigs = (ConcurrentMap<String, PortGroupConfig>) map;
1277         } else {
1278             log.error("Retrieval of frm.portGroupConfigs cache failed for Container {}", container.getName());
1279         }
1280
1281         map = clusterContainerService.getCache("frm.portGroupData");
1282         if (map != null) {
1283             portGroupData = (ConcurrentMap<PortGroupConfig, Map<Node, PortGroup>>) map;
1284         } else {
1285             log.error("Retrieval of frm.portGroupData allocation failed for Container {}", container.getName());
1286         }
1287
1288         map = clusterContainerService.getCache("frm.TSPolicies");
1289         if (map != null) {
1290             TSPolicies = (ConcurrentMap<String, Object>) map;
1291         } else {
1292             log.error("Retrieval of frm.TSPolicies cache failed for Container {}", container.getName());
1293         }
1294
1295     }
1296
1297     private boolean flowConfigExists(FlowConfig config) {
1298         // Flow name has to be unique on per node id basis
1299         for (ConcurrentMap.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
1300             if (entry.getValue().isByNameAndNodeIdEqual(config)) {
1301                 return true;
1302             }
1303         }
1304         return false;
1305     }
1306
1307     @Override
1308     public Status addStaticFlow(FlowConfig config) {
1309         // Configuration object validation
1310         Status status = config.validate(container);
1311         if (!status.isSuccess()) {
1312             log.warn("Invalid Configuration for flow {}. The failure is {}", config, status.getDescription());
1313             String error = "Invalid Configuration (" + status.getDescription() + ")";
1314             config.setStatus(error);
1315             return new Status(StatusCode.BADREQUEST, error);
1316         }
1317         return addStaticFlowInternal(config, false);
1318     }
1319
1320     /**
1321      * Private method to add a static flow configuration which does not run any
1322      * validation on the passed FlowConfig object. If restore is set to true,
1323      * configuration is stored in configuration database regardless the
1324      * installation on the network node was successful. This is useful at boot
1325      * when static flows are present in startup configuration and are read
1326      * before the switches connects.
1327      *
1328      * @param config
1329      *            The static flow configuration
1330      * @param restore
1331      *            if true, the configuration is stored regardless the
1332      *            installation on the network node was successful
1333      * @return The status of this request
1334      */
1335     private Status addStaticFlowInternal(FlowConfig config, boolean restore) {
1336         boolean multipleFlowPush = false;
1337         String error;
1338         Status status;
1339         config.setStatus(SUCCESS);
1340
1341         // Presence check
1342         if (flowConfigExists(config)) {
1343             error = "Entry with this name on specified switch already exists";
1344             log.warn("Entry with this name on specified switch already exists: {}", config);
1345             config.setStatus(error);
1346             return new Status(StatusCode.CONFLICT, error);
1347         }
1348
1349         if ((config.getIngressPort() == null) && config.getPortGroup() != null) {
1350             for (String portGroupName : portGroupConfigs.keySet()) {
1351                 if (portGroupName.equalsIgnoreCase(config.getPortGroup())) {
1352                     multipleFlowPush = true;
1353                     break;
1354                 }
1355             }
1356             if (!multipleFlowPush) {
1357                 log.warn("Invalid Configuration(Invalid PortGroup Name) for flow {}", config);
1358                 error = "Invalid Configuration (Invalid PortGroup Name)";
1359                 config.setStatus(error);
1360                 return new Status(StatusCode.BADREQUEST, error);
1361             }
1362         }
1363
1364         /*
1365          * If requested program the entry in hardware first before updating the
1366          * StaticFlow DB
1367          */
1368         if (!multipleFlowPush) {
1369             // Program hw
1370             if (config.installInHw()) {
1371                 FlowEntry entry = config.getFlowEntry();
1372                 status = this.installFlowEntry(entry);
1373                 if (!status.isSuccess()) {
1374                     config.setStatus(status.getDescription());
1375                     if (!restore) {
1376                         return status;
1377                     }
1378                 }
1379             }
1380         }
1381
1382         /*
1383          * When the control reaches this point, either of the following
1384          * conditions is true 1. This is a single entry configuration (non
1385          * PortGroup) and the hardware installation is successful 2. This is a
1386          * multiple entry configuration (PortGroup) and hardware installation is
1387          * NOT done directly on this event. 3. The User prefers to retain the
1388          * configuration in Controller and skip hardware installation.
1389          *
1390          * Hence it is safe to update the StaticFlow DB at this point.
1391          *
1392          * Note : For the case of PortGrouping, it is essential to have this DB
1393          * populated before the PortGroupListeners can query for the DB
1394          * triggered using portGroupChanged event...
1395          */
1396         Integer ordinal = staticFlowsOrdinal.get(0);
1397         staticFlowsOrdinal.put(0, ++ordinal);
1398         staticFlows.put(ordinal, config);
1399
1400         if (multipleFlowPush) {
1401             PortGroupConfig pgconfig = portGroupConfigs.get(config.getPortGroup());
1402             Map<Node, PortGroup> existingData = portGroupData.get(pgconfig);
1403             if (existingData != null) {
1404                 portGroupChanged(pgconfig, existingData, true);
1405             }
1406         }
1407         return new Status(StatusCode.SUCCESS);
1408     }
1409
1410     private void addStaticFlowsToSwitch(Node node) {
1411         for (ConcurrentMap.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
1412             FlowConfig config = entry.getValue();
1413             if (config.isPortGroupEnabled()) {
1414                 continue;
1415             }
1416             if (config.getNode().equals(node)) {
1417                 if (config.installInHw() && !config.getStatus().equals(SUCCESS)) {
1418                     Status status = this.installFlowEntryAsync(config.getFlowEntry());
1419                     config.setStatus(status.getDescription());
1420                 }
1421             }
1422         }
1423         // Update cluster cache
1424         refreshClusterStaticFlowsStatus(node);
1425     }
1426
1427     private void updateStaticFlowConfigsOnNodeDown(Node node) {
1428         log.trace("Updating Static Flow configs on node down: {}", node);
1429
1430         List<Integer> toRemove = new ArrayList<Integer>();
1431         for (Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
1432
1433             FlowConfig config = entry.getValue();
1434
1435             if (config.isPortGroupEnabled()) {
1436                 continue;
1437             }
1438
1439             if (config.installInHw() && config.getNode().equals(node)) {
1440                 if (config.isInternalFlow()) {
1441                     // Take note of this controller generated static flow
1442                     toRemove.add(entry.getKey());
1443                 } else {
1444                     config.setStatus(NODEDOWN);
1445                 }
1446             }
1447         }
1448         // Remove controller generated static flows for this node
1449         for (Integer index : toRemove) {
1450             staticFlows.remove(index);
1451         }
1452         // Update cluster cache
1453         refreshClusterStaticFlowsStatus(node);
1454
1455     }
1456
1457     private void updateStaticFlowConfigsOnContainerModeChange(UpdateType update) {
1458         log.trace("Updating Static Flow configs on container mode change: {}", update);
1459
1460         for (ConcurrentMap.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
1461             FlowConfig config = entry.getValue();
1462             if (config.isPortGroupEnabled()) {
1463                 continue;
1464             }
1465             if (config.installInHw() && !config.isInternalFlow()) {
1466                 switch (update) {
1467                 case ADDED:
1468                     config.setStatus("Removed from node because in container mode");
1469                     break;
1470                 case REMOVED:
1471                     config.setStatus(SUCCESS);
1472                     break;
1473                 default:
1474                 }
1475             }
1476         }
1477         // Update cluster cache
1478         refreshClusterStaticFlowsStatus(null);
1479     }
1480
1481     @Override
1482     public Status removeStaticFlow(FlowConfig config) {
1483         /*
1484          * No config.isInternal() check as NB does not take this path and GUI
1485          * cannot issue a delete on an internal generated flow. We need this
1486          * path to be accessible when switch mode is changed from proactive to
1487          * reactive, so that we can remove the internal generated LLDP and ARP
1488          * punt flows
1489          */
1490
1491         // Look for the target configuration entry
1492         Integer key = 0;
1493         FlowConfig target = null;
1494         for (ConcurrentMap.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
1495             if (entry.getValue().isByNameAndNodeIdEqual(config)) {
1496                 key = entry.getKey();
1497                 target = entry.getValue();
1498                 break;
1499             }
1500         }
1501         if (target == null) {
1502             return new Status(StatusCode.NOTFOUND, "Entry Not Present");
1503         }
1504
1505         // Program the network node
1506         Status status = this.uninstallFlowEntry(config.getFlowEntry());
1507
1508         // Update configuration database if programming was successful
1509         if (status.isSuccess()) {
1510             staticFlows.remove(key);
1511         }
1512
1513         return status;
1514     }
1515
1516     @Override
1517     public Status removeStaticFlow(String name, Node node) {
1518         // Look for the target configuration entry
1519         Integer key = 0;
1520         FlowConfig target = null;
1521         for (ConcurrentMap.Entry<Integer, FlowConfig> mapEntry : staticFlows.entrySet()) {
1522             if (mapEntry.getValue().isByNameAndNodeIdEqual(name, node)) {
1523                 key = mapEntry.getKey();
1524                 target = mapEntry.getValue();
1525                 break;
1526             }
1527         }
1528         if (target == null) {
1529             return new Status(StatusCode.NOTFOUND, "Entry Not Present");
1530         }
1531
1532         // Validity check for api3 entry point
1533         if (target.isInternalFlow()) {
1534             String msg = "Invalid operation: Controller generated flow cannot be deleted";
1535             String logMsg = msg + ": {}";
1536             log.warn(logMsg, name);
1537             return new Status(StatusCode.NOTACCEPTABLE, msg);
1538         }
1539
1540         if (target.isPortGroupEnabled()) {
1541             String msg = "Invalid operation: Port Group flows cannot be deleted through this API";
1542             String logMsg = msg + ": {}";
1543             log.warn(logMsg, name);
1544             return new Status(StatusCode.NOTACCEPTABLE, msg);
1545         }
1546
1547         // Program the network node
1548         Status status = this.removeEntry(target.getFlowEntry(), false);
1549
1550         // Update configuration database if programming was successful
1551         if (status.isSuccess()) {
1552             staticFlows.remove(key);
1553         }
1554
1555         return status;
1556     }
1557
1558     @Override
1559     public Status modifyStaticFlow(FlowConfig newFlowConfig) {
1560         // Validity check for api3 entry point
1561         if (newFlowConfig.isInternalFlow()) {
1562             String msg = "Invalid operation: Controller generated flow cannot be modified";
1563             String logMsg = msg + ": {}";
1564             log.warn(logMsg, newFlowConfig);
1565             return new Status(StatusCode.NOTACCEPTABLE, msg);
1566         }
1567
1568         // Validity Check
1569         Status status = newFlowConfig.validate(container);
1570         if (!status.isSuccess()) {
1571             String msg = "Invalid Configuration (" + status.getDescription() + ")";
1572             newFlowConfig.setStatus(msg);
1573             log.warn("Invalid Configuration for flow {}. The failure is {}", newFlowConfig, status.getDescription());
1574             return new Status(StatusCode.BADREQUEST, msg);
1575         }
1576
1577         FlowConfig oldFlowConfig = null;
1578         Integer index = null;
1579         for (ConcurrentMap.Entry<Integer, FlowConfig> mapEntry : staticFlows.entrySet()) {
1580             FlowConfig entry = mapEntry.getValue();
1581             if (entry.isByNameAndNodeIdEqual(newFlowConfig.getName(), newFlowConfig.getNode())) {
1582                 oldFlowConfig = entry;
1583                 index = mapEntry.getKey();
1584                 break;
1585             }
1586         }
1587
1588         if (oldFlowConfig == null) {
1589             String msg = "Attempt to modify a non existing static flow";
1590             String logMsg = msg + ": {}";
1591             log.warn(logMsg, newFlowConfig);
1592             return new Status(StatusCode.NOTFOUND, msg);
1593         }
1594
1595         // Do not attempt to reinstall the flow, warn user
1596         if (newFlowConfig.equals(oldFlowConfig)) {
1597             String msg = "No modification detected";
1598             log.info("Static flow modification skipped. New flow and old flow are the same: {}", newFlowConfig);
1599             return new Status(StatusCode.SUCCESS, msg);
1600         }
1601
1602         // If flow is installed, program the network node
1603         status = new Status(StatusCode.SUCCESS, "Saved in config");
1604         if (oldFlowConfig.installInHw()) {
1605             status = this.modifyFlowEntry(oldFlowConfig.getFlowEntry(), newFlowConfig.getFlowEntry());
1606         }
1607
1608         // Update configuration database if programming was successful
1609         if (status.isSuccess()) {
1610             newFlowConfig.setStatus(status.getDescription());
1611             staticFlows.put(index, newFlowConfig);
1612         }
1613
1614         return status;
1615     }
1616
1617     @Override
1618     public Status toggleStaticFlowStatus(String name, Node node) {
1619         return toggleStaticFlowStatus(getStaticFlow(name, node));
1620     }
1621
1622     @Override
1623     public Status toggleStaticFlowStatus(FlowConfig config) {
1624         if (config == null) {
1625             String msg = "Invalid request: null flow config";
1626             log.warn(msg);
1627             return new Status(StatusCode.BADREQUEST, msg);
1628         }
1629         // Validity check for api3 entry point
1630         if (config.isInternalFlow()) {
1631             String msg = "Invalid operation: Controller generated flow cannot be modified";
1632             String logMsg = msg + ": {}";
1633             log.warn(logMsg, config);
1634             return new Status(StatusCode.NOTACCEPTABLE, msg);
1635         }
1636
1637         // Find the config entry
1638         Integer key = 0;
1639         FlowConfig target = null;
1640         for (Map.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
1641             FlowConfig conf = entry.getValue();
1642             if (conf.isByNameAndNodeIdEqual(config)) {
1643                 key = entry.getKey();
1644                 target = conf;
1645                 break;
1646             }
1647         }
1648         if (target != null) {
1649             // Program the network node
1650             Status status = (target.installInHw()) ? this.uninstallFlowEntry(target.getFlowEntry()) : this
1651                     .installFlowEntry(target.getFlowEntry());
1652             if (status.isSuccess()) {
1653                 // Update Configuration database
1654                 target.setStatus(SUCCESS);
1655                 target.toggleInstallation();
1656                 staticFlows.put(key, target);
1657             }
1658             return status;
1659         }
1660
1661         return new Status(StatusCode.NOTFOUND, "Unable to locate the entry. Failed to toggle status");
1662     }
1663
1664     /**
1665      * Reinsert all static flows entries in the cache to force cache updates in
1666      * the cluster. This is useful when only some parameters were changed in the
1667      * entries, like the status.
1668      *
1669      * @param node
1670      *            The node for which the static flow configurations have to be
1671      *            refreshed. If null, all nodes static flows will be refreshed.
1672      */
1673     private void refreshClusterStaticFlowsStatus(Node node) {
1674         // Refresh cluster cache
1675         for (ConcurrentMap.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
1676             if (node == null || entry.getValue().getNode().equals(node)) {
1677                 staticFlows.put(entry.getKey(), entry.getValue());
1678             }
1679         }
1680     }
1681
1682     /**
1683      * Uninstall all the non-internal Flow Entries present in the software view.
1684      * A copy of each entry is stored in the inactive list so that it can be
1685      * re-applied when needed. This function is called on the global instance of
1686      * FRM only, when the first container is created
1687      */
1688     private void uninstallAllFlowEntries() {
1689         log.info("Uninstalling all non-internal flows");
1690
1691         // Store entries / create target list
1692         for (ConcurrentMap.Entry<FlowEntryInstall, FlowEntryInstall> mapEntry : installedSwView.entrySet()) {
1693             FlowEntryInstall flowEntries = mapEntry.getValue();
1694             // Skip internal generated static flows
1695             if (!flowEntries.isInternal()) {
1696                 inactiveFlows.put(flowEntries.getOriginal(), flowEntries.getOriginal());
1697             }
1698         }
1699
1700         // Now remove the entries
1701         for (FlowEntry flowEntry : inactiveFlows.keySet()) {
1702             Status status = this.removeEntry(flowEntry, false);
1703             if (!status.isSuccess()) {
1704                 log.warn("Failed to remove entry: {}. The failure is: {}", flowEntry, status.getDescription());
1705             }
1706         }
1707     }
1708
1709     /**
1710      * Re-install all the Flow Entries present in the inactive list The inactive
1711      * list will be empty at the end of this call This function is called on the
1712      * default container instance of FRM only when the last container is deleted
1713      */
1714     private void reinstallAllFlowEntries() {
1715         log.info("Reinstalling all inactive flows");
1716
1717         for (FlowEntry flowEntry : this.inactiveFlows.keySet()) {
1718             this.addEntry(flowEntry, false);
1719         }
1720
1721         // Empty inactive list in any case
1722         inactiveFlows.clear();
1723     }
1724
1725     @Override
1726     public List<FlowConfig> getStaticFlows() {
1727         return getStaticFlowsOrderedList(staticFlows, staticFlowsOrdinal.get(0).intValue());
1728     }
1729
1730     // TODO: need to come out with a better algorithm for maintaining the order
1731     // of the configuration entries
1732     // with actual one, index associated to deleted entries cannot be reused and
1733     // map grows...
1734     private List<FlowConfig> getStaticFlowsOrderedList(ConcurrentMap<Integer, FlowConfig> flowMap, int maxKey) {
1735         List<FlowConfig> orderedList = new ArrayList<FlowConfig>();
1736         for (int i = 0; i <= maxKey; i++) {
1737             FlowConfig entry = flowMap.get(i);
1738             if (entry != null) {
1739                 orderedList.add(entry);
1740             }
1741         }
1742         return orderedList;
1743     }
1744
1745     @Override
1746     public FlowConfig getStaticFlow(String name, Node node) {
1747         for (ConcurrentMap.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
1748             if (entry.getValue().isByNameAndNodeIdEqual(name, node)) {
1749                 return entry.getValue();
1750             }
1751         }
1752         return null;
1753     }
1754
1755     @Override
1756     public List<FlowConfig> getStaticFlows(Node node) {
1757         List<FlowConfig> list = new ArrayList<FlowConfig>();
1758         for (ConcurrentMap.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
1759             if (entry.getValue().onNode(node)) {
1760                 list.add(entry.getValue());
1761             }
1762         }
1763         return list;
1764     }
1765
1766     @Override
1767     public List<String> getStaticFlowNamesForNode(Node node) {
1768         List<String> list = new ArrayList<String>();
1769         for (ConcurrentMap.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
1770             if (entry.getValue().onNode(node)) {
1771                 list.add(entry.getValue().getName());
1772             }
1773         }
1774         return list;
1775     }
1776
1777     @Override
1778     public List<Node> getListNodeWithConfiguredFlows() {
1779         Set<Node> set = new HashSet<Node>();
1780         for (ConcurrentMap.Entry<Integer, FlowConfig> entry : staticFlows.entrySet()) {
1781             set.add(entry.getValue().getNode());
1782         }
1783         return new ArrayList<Node>(set);
1784     }
1785
1786     @SuppressWarnings("unchecked")
1787     private void loadFlowConfiguration() {
1788         ObjectReader objReader = new ObjectReader();
1789         ConcurrentMap<Integer, FlowConfig> confList = (ConcurrentMap<Integer, FlowConfig>) objReader.read(this,
1790                 frmFileName);
1791
1792         ConcurrentMap<String, PortGroupConfig> pgConfig = (ConcurrentMap<String, PortGroupConfig>) objReader.read(this,
1793                 portGroupFileName);
1794
1795         if (pgConfig != null) {
1796             for (ConcurrentMap.Entry<String, PortGroupConfig> entry : pgConfig.entrySet()) {
1797                 addPortGroupConfig(entry.getKey(), entry.getValue().getMatchString(), true);
1798             }
1799         }
1800
1801         if (confList == null) {
1802             return;
1803         }
1804
1805         int maxKey = 0;
1806         for (Integer key : confList.keySet()) {
1807             if (key.intValue() > maxKey) {
1808                 maxKey = key.intValue();
1809             }
1810         }
1811
1812         for (FlowConfig conf : getStaticFlowsOrderedList(confList, maxKey)) {
1813             addStaticFlowInternal(conf, true);
1814         }
1815     }
1816
1817     @Override
1818     public Object readObject(ObjectInputStream ois) throws FileNotFoundException, IOException, ClassNotFoundException {
1819         return ois.readObject();
1820     }
1821
1822     @Override
1823     public Status saveConfig() {
1824         return saveConfigInternal();
1825     }
1826
1827     private Status saveConfigInternal() {
1828         ObjectWriter objWriter = new ObjectWriter();
1829         ConcurrentMap<Integer, FlowConfig> nonDynamicFlows = new ConcurrentHashMap<Integer, FlowConfig>();
1830         for (Integer ordinal : staticFlows.keySet()) {
1831             FlowConfig config = staticFlows.get(ordinal);
1832             // Do not save dynamic and controller generated static flows
1833             if (config.isDynamic() || config.isInternalFlow()) {
1834                 continue;
1835             }
1836             nonDynamicFlows.put(ordinal, config);
1837         }
1838         objWriter.write(nonDynamicFlows, frmFileName);
1839         objWriter.write(new ConcurrentHashMap<String, PortGroupConfig>(portGroupConfigs), portGroupFileName);
1840         return new Status(StatusCode.SUCCESS, null);
1841     }
1842
1843     @Override
1844     public void subnetNotify(Subnet sub, boolean add) {
1845     }
1846
1847     private void installImplicitARPReplyPunt(Node node) {
1848
1849         if (node == null) {
1850             return;
1851         }
1852
1853         List<String> puntAction = new ArrayList<String>();
1854         puntAction.add(ActionType.CONTROLLER.toString());
1855
1856         FlowConfig allowARP = new FlowConfig();
1857         allowARP.setInstallInHw(true);
1858         allowARP.setName(FlowConfig.INTERNALSTATICFLOWBEGIN + "Punt ARP Reply" + FlowConfig.INTERNALSTATICFLOWEND);
1859         allowARP.setPriority("500");
1860         allowARP.setNode(node);
1861         allowARP.setEtherType("0x" + Integer.toHexString(EtherTypes.ARP.intValue()).toUpperCase());
1862         allowARP.setDstMac(HexEncode.bytesToHexString(switchManager.getControllerMAC()));
1863         allowARP.setActions(puntAction);
1864         addStaticFlowInternal(allowARP, true); // skip validation on internal static flow name
1865     }
1866
1867     @Override
1868     public void modeChangeNotify(Node node, boolean proactive) {
1869         List<FlowConfig> defaultConfigs = new ArrayList<FlowConfig>();
1870
1871         List<String> puntAction = new ArrayList<String>();
1872         puntAction.add(ActionType.CONTROLLER.toString());
1873
1874         FlowConfig allowARP = new FlowConfig();
1875         allowARP.setInstallInHw(true);
1876         allowARP.setName(FlowConfig.INTERNALSTATICFLOWBEGIN + "Punt ARP" + FlowConfig.INTERNALSTATICFLOWEND);
1877         allowARP.setPriority("1");
1878         allowARP.setNode(node);
1879         allowARP.setEtherType("0x" + Integer.toHexString(EtherTypes.ARP.intValue()).toUpperCase());
1880         allowARP.setActions(puntAction);
1881         defaultConfigs.add(allowARP);
1882
1883         FlowConfig allowLLDP = new FlowConfig();
1884         allowLLDP.setInstallInHw(true);
1885         allowLLDP.setName(FlowConfig.INTERNALSTATICFLOWBEGIN + "Punt LLDP" + FlowConfig.INTERNALSTATICFLOWEND);
1886         allowLLDP.setPriority("1");
1887         allowLLDP.setNode(node);
1888         allowLLDP.setEtherType("0x" + Integer.toHexString(EtherTypes.LLDP.intValue()).toUpperCase());
1889         allowLLDP.setActions(puntAction);
1890         defaultConfigs.add(allowLLDP);
1891
1892         List<String> dropAction = new ArrayList<String>();
1893         dropAction.add(ActionType.DROP.toString());
1894
1895         FlowConfig dropAllConfig = new FlowConfig();
1896         dropAllConfig.setInstallInHw(true);
1897         dropAllConfig.setName(FlowConfig.INTERNALSTATICFLOWBEGIN + "Catch-All Drop" + FlowConfig.INTERNALSTATICFLOWEND);
1898         dropAllConfig.setPriority("0");
1899         dropAllConfig.setNode(node);
1900         dropAllConfig.setActions(dropAction);
1901         defaultConfigs.add(dropAllConfig);
1902
1903         log.info("Forwarding mode for node {} set to {}", node, (proactive ? "proactive" : "reactive"));
1904         for (FlowConfig fc : defaultConfigs) {
1905             Status status = (proactive) ? addStaticFlowInternal(fc, false) : removeStaticFlow(fc);
1906             if (status.isSuccess()) {
1907                 log.info("{} Proactive Static flow: {}", (proactive ? "Installed" : "Removed"), fc.getName());
1908             } else {
1909                 log.warn("Failed to {} Proactive Static flow: {}", (proactive ? "install" : "remove"), fc.getName());
1910             }
1911         }
1912     }
1913
1914     /**
1915      * Remove from the databases all the flows installed on the node
1916      *
1917      * @param node
1918      */
1919     private void cleanDatabaseForNode(Node node) {
1920         log.info("Cleaning Flow database for Node {}", node);
1921         if (nodeFlows.containsKey(node)) {
1922             List<FlowEntryInstall> toRemove = new ArrayList<FlowEntryInstall>(nodeFlows.get(node));
1923
1924             for (FlowEntryInstall entry : toRemove) {
1925                 updateLocalDatabase(entry, false);
1926             }
1927         }
1928     }
1929
1930     private boolean doesFlowContainNodeConnector(Flow flow, NodeConnector nc) {
1931         if (nc == null) {
1932             return false;
1933         }
1934
1935         Match match = flow.getMatch();
1936         if (match.isPresent(MatchType.IN_PORT)) {
1937             NodeConnector matchPort = (NodeConnector) match.getField(MatchType.IN_PORT).getValue();
1938             if (matchPort.equals(nc)) {
1939                 return true;
1940             }
1941         }
1942         List<Action> actionsList = flow.getActions();
1943         if (actionsList != null) {
1944             for (Action action : actionsList) {
1945                 if (action instanceof Output) {
1946                     NodeConnector actionPort = ((Output) action).getPort();
1947                     if (actionPort.equals(nc)) {
1948                         return true;
1949                     }
1950                 }
1951             }
1952         }
1953         return false;
1954     }
1955
1956     @Override
1957     public void notifyNode(Node node, UpdateType type, Map<String, Property> propMap) {
1958         this.pendingEvents.offer(new NodeUpdateEvent(type, node));
1959     }
1960
1961     @Override
1962     public void notifyNodeConnector(NodeConnector nodeConnector, UpdateType type, Map<String, Property> propMap) {
1963
1964     }
1965
1966     private FlowConfig getDerivedFlowConfig(FlowConfig original, String configName, Short port) {
1967         FlowConfig derivedFlow = new FlowConfig(original);
1968         derivedFlow.setDynamic(true);
1969         derivedFlow.setPortGroup(null);
1970         derivedFlow.setName(original.getName() + "_" + configName + "_" + port);
1971         derivedFlow.setIngressPort(port + "");
1972         return derivedFlow;
1973     }
1974
1975     private void addPortGroupFlows(PortGroupConfig config, Node node, PortGroup data) {
1976         for (FlowConfig staticFlow : staticFlows.values()) {
1977             if (staticFlow.getPortGroup() == null) {
1978                 continue;
1979             }
1980             if ((staticFlow.getNode().equals(node)) && (staticFlow.getPortGroup().equals(config.getName()))) {
1981                 for (Short port : data.getPorts()) {
1982                     FlowConfig derivedFlow = getDerivedFlowConfig(staticFlow, config.getName(), port);
1983                     addStaticFlowInternal(derivedFlow, false);
1984                 }
1985             }
1986         }
1987     }
1988
1989     private void removePortGroupFlows(PortGroupConfig config, Node node, PortGroup data) {
1990         for (FlowConfig staticFlow : staticFlows.values()) {
1991             if (staticFlow.getPortGroup() == null) {
1992                 continue;
1993             }
1994             if (staticFlow.getNode().equals(node) && staticFlow.getPortGroup().equals(config.getName())) {
1995                 for (Short port : data.getPorts()) {
1996                     FlowConfig derivedFlow = getDerivedFlowConfig(staticFlow, config.getName(), port);
1997                     removeStaticFlow(derivedFlow);
1998                 }
1999             }
2000         }
2001     }
2002
2003     @Override
2004     public void portGroupChanged(PortGroupConfig config, Map<Node, PortGroup> data, boolean add) {
2005         log.info("PortGroup Changed for: {} Data: {}", config, portGroupData);
2006         Map<Node, PortGroup> existingData = portGroupData.get(config);
2007         if (existingData != null) {
2008             for (Map.Entry<Node, PortGroup> entry : data.entrySet()) {
2009                 PortGroup existingPortGroup = existingData.get(entry.getKey());
2010                 if (existingPortGroup == null) {
2011                     if (add) {
2012                         existingData.put(entry.getKey(), entry.getValue());
2013                         addPortGroupFlows(config, entry.getKey(), entry.getValue());
2014                     }
2015                 } else {
2016                     if (add) {
2017                         existingPortGroup.getPorts().addAll(entry.getValue().getPorts());
2018                         addPortGroupFlows(config, entry.getKey(), entry.getValue());
2019                     } else {
2020                         existingPortGroup.getPorts().removeAll(entry.getValue().getPorts());
2021                         removePortGroupFlows(config, entry.getKey(), entry.getValue());
2022                     }
2023                 }
2024             }
2025         } else {
2026             if (add) {
2027                 portGroupData.put(config, data);
2028                 for (Node swid : data.keySet()) {
2029                     addPortGroupFlows(config, swid, data.get(swid));
2030                 }
2031             }
2032         }
2033     }
2034
2035     @Override
2036     public boolean addPortGroupConfig(String name, String regex, boolean restore) {
2037         PortGroupConfig config = portGroupConfigs.get(name);
2038         if (config != null) {
2039             return false;
2040         }
2041
2042         if ((portGroupProvider == null) && !restore) {
2043             return false;
2044         }
2045         if ((portGroupProvider != null) && (!portGroupProvider.isMatchCriteriaSupported(regex))) {
2046             return false;
2047         }
2048
2049         config = new PortGroupConfig(name, regex);
2050         portGroupConfigs.put(name, config);
2051         if (portGroupProvider != null) {
2052             portGroupProvider.createPortGroupConfig(config);
2053         }
2054         return true;
2055     }
2056
2057     @Override
2058     public boolean delPortGroupConfig(String name) {
2059         PortGroupConfig config = portGroupConfigs.get(name);
2060         if (config == null) {
2061             return false;
2062         }
2063
2064         if (portGroupProvider != null) {
2065             portGroupProvider.deletePortGroupConfig(config);
2066         }
2067         portGroupConfigs.remove(name);
2068         return true;
2069     }
2070
2071     private void usePortGroupConfig(String name) {
2072         PortGroupConfig config = portGroupConfigs.get(name);
2073         if (config == null) {
2074             return;
2075         }
2076         if (portGroupProvider != null) {
2077             Map<Node, PortGroup> data = portGroupProvider.getPortGroupData(config);
2078             portGroupData.put(config, data);
2079         }
2080     }
2081
2082     @Override
2083     public Map<String, PortGroupConfig> getPortGroupConfigs() {
2084         return portGroupConfigs;
2085     }
2086
2087     public boolean isPortGroupSupported() {
2088         if (portGroupProvider == null) {
2089             return false;
2090         }
2091         return true;
2092     }
2093
2094     public void setIContainer(IContainer s) {
2095         this.container = s;
2096     }
2097
2098     public void unsetIContainer(IContainer s) {
2099         if (this.container == s) {
2100             this.container = null;
2101         }
2102     }
2103
2104     @Override
2105     public PortGroupProvider getPortGroupProvider() {
2106         return portGroupProvider;
2107     }
2108
2109     public void unsetPortGroupProvider(PortGroupProvider portGroupProvider) {
2110         this.portGroupProvider = null;
2111     }
2112
2113     public void setPortGroupProvider(PortGroupProvider portGroupProvider) {
2114         this.portGroupProvider = portGroupProvider;
2115         portGroupProvider.registerPortGroupChange(this);
2116         for (PortGroupConfig config : portGroupConfigs.values()) {
2117             portGroupProvider.createPortGroupConfig(config);
2118         }
2119     }
2120
2121     public void setFrmAware(IForwardingRulesManagerAware obj) {
2122         this.frmAware.add(obj);
2123     }
2124
2125     public void unsetFrmAware(IForwardingRulesManagerAware obj) {
2126         this.frmAware.remove(obj);
2127     }
2128
2129     void setClusterContainerService(IClusterContainerServices s) {
2130         log.debug("Cluster Service set");
2131         this.clusterContainerService = s;
2132     }
2133
2134     void unsetClusterContainerService(IClusterContainerServices s) {
2135         if (this.clusterContainerService == s) {
2136             log.debug("Cluster Service removed!");
2137             this.clusterContainerService = null;
2138         }
2139     }
2140
2141     private String getContainerName() {
2142         if (container == null) {
2143             return GlobalConstants.DEFAULT.toString();
2144         }
2145         return container.getName();
2146     }
2147
2148     /**
2149      * Function called by the dependency manager when all the required
2150      * dependencies are satisfied
2151      *
2152      */
2153     void init() {
2154         frmFileName = GlobalConstants.STARTUPHOME.toString() + "frm_staticflows_" + this.getContainerName() + ".conf";
2155         portGroupFileName = GlobalConstants.STARTUPHOME.toString() + "portgroup_" + this.getContainerName() + ".conf";
2156
2157         inContainerMode = false;
2158
2159         if (portGroupProvider != null) {
2160             portGroupProvider.registerPortGroupChange(this);
2161         }
2162
2163         cacheStartup();
2164
2165         registerWithOSGIConsole();
2166
2167         /*
2168          * If we are not the first cluster node to come up, do not initialize
2169          * the static flow entries ordinal
2170          */
2171         if (staticFlowsOrdinal.size() == 0) {
2172             staticFlowsOrdinal.put(0, Integer.valueOf(0));
2173         }
2174
2175         pendingEvents = new LinkedBlockingQueue<FRMEvent>();
2176
2177         // Initialize the event handler thread
2178         frmEventHandler = new Thread(new Runnable() {
2179             @Override
2180             public void run() {
2181                 while (!stopping) {
2182                     try {
2183                         FRMEvent event = pendingEvents.take();
2184                         if (event == null) {
2185                             log.warn("Dequeued null event");
2186                             continue;
2187                         }
2188                         if (event instanceof NodeUpdateEvent) {
2189                             NodeUpdateEvent update = (NodeUpdateEvent) event;
2190                             Node node = update.getNode();
2191                             switch (update.getUpdateType()) {
2192                             case ADDED:
2193                                 addStaticFlowsToSwitch(node);
2194                                 break;
2195                             case REMOVED:
2196                                 cleanDatabaseForNode(node);
2197                                 updateStaticFlowConfigsOnNodeDown(node);
2198                                 break;
2199                             default:
2200                             }
2201                         } else if (event instanceof ErrorReportedEvent) {
2202                             ErrorReportedEvent errEvent = (ErrorReportedEvent) event;
2203                             processErrorEvent(errEvent);
2204                         } else {
2205                             log.warn("Dequeued unknown event {}", event.getClass().getSimpleName());
2206                         }
2207                     } catch (InterruptedException e) {
2208                         log.warn("FRM EventHandler thread interrupted", e);
2209                     }
2210                 }
2211             }
2212         }, "FRM EventHandler Collector");
2213     }
2214
2215     /**
2216      * Function called by the dependency manager when at least one dependency
2217      * become unsatisfied or when the component is shutting down because for
2218      * example bundle is being stopped.
2219      *
2220      */
2221     void destroy() {
2222         frmAware.clear();
2223     }
2224
2225     /**
2226      * Function called by dependency manager after "init ()" is called and after
2227      * the services provided by the class are registered in the service registry
2228      *
2229      */
2230     void start() {
2231         // Initialize graceful stop flag
2232         stopping = false;
2233
2234         // Start event handler thread
2235         frmEventHandler.start();
2236
2237         /*
2238          * Read startup and build database if we have not already gotten the
2239          * configurations synced from another node
2240          */
2241         if (staticFlows.isEmpty()) {
2242             loadFlowConfiguration();
2243         }
2244     }
2245
2246     /**
2247      * Function called by the dependency manager before the services exported by
2248      * the component are unregistered, this will be followed by a "destroy ()"
2249      * calls
2250      */
2251     void stop() {
2252         stopping = true;
2253         uninstallAllFlowEntries();
2254     }
2255
2256     public void setFlowProgrammerService(IFlowProgrammerService service) {
2257         this.programmer = service;
2258     }
2259
2260     public void unsetFlowProgrammerService(IFlowProgrammerService service) {
2261         if (this.programmer == service) {
2262             this.programmer = null;
2263         }
2264     }
2265
2266     public void setSwitchManager(ISwitchManager switchManager) {
2267         this.switchManager = switchManager;
2268     }
2269
2270     public void unsetSwitchManager(ISwitchManager switchManager) {
2271         if (this.switchManager == switchManager) {
2272             this.switchManager = null;
2273         }
2274     }
2275
2276     @Override
2277     public void tagUpdated(String containerName, Node n, short oldTag, short newTag, UpdateType t) {
2278         if (!container.getName().equals(containerName)) {
2279             return;
2280         }
2281     }
2282
2283     @Override
2284     public void containerFlowUpdated(String containerName, ContainerFlow previous, ContainerFlow current, UpdateType t) {
2285         if (!container.getName().equals(containerName)) {
2286             return;
2287         }
2288         log.trace("Container {}: Updating installed flows because of container flow change: {} {}",
2289                 container.getName(), t, current);
2290         /*
2291          * Whether it is an addition or removal, we have to recompute the merged
2292          * flows entries taking into account all the current container flows
2293          * because flow merging is not an injective function
2294          */
2295         updateFlowsContainerFlow();
2296     }
2297
2298     @Override
2299     public void nodeConnectorUpdated(String containerName, NodeConnector nc, UpdateType t) {
2300         if (!container.getName().equals(containerName)) {
2301             return;
2302         }
2303
2304         boolean updateStaticFlowCluster = false;
2305
2306         switch (t) {
2307         case REMOVED:
2308
2309             List<FlowEntryInstall> nodeFlowEntries = nodeFlows.get(nc.getNode());
2310             if (nodeFlowEntries == null) {
2311                 return;
2312             }
2313             for (FlowEntryInstall fei : new ArrayList<FlowEntryInstall>(nodeFlowEntries)) {
2314                 if (doesFlowContainNodeConnector(fei.getInstall().getFlow(), nc)) {
2315                     Status status = this.removeEntryInternal(fei, true);
2316                     if (!status.isSuccess()) {
2317                         continue;
2318                     }
2319                     /*
2320                      * If the flow entry is a static flow, then update its
2321                      * configuration
2322                      */
2323                     if (fei.getGroupName().equals(FlowConfig.STATICFLOWGROUP)) {
2324                         FlowConfig flowConfig = getStaticFlow(fei.getFlowName(), fei.getNode());
2325                         if (flowConfig != null) {
2326                             flowConfig.setStatus(PORTREMOVED);
2327                             updateStaticFlowCluster = true;
2328                         }
2329                     }
2330                 }
2331             }
2332             if (updateStaticFlowCluster) {
2333                 refreshClusterStaticFlowsStatus(nc.getNode());
2334             }
2335             break;
2336         case ADDED:
2337             List<FlowConfig> flowConfigForNode = getStaticFlows(nc.getNode());
2338             for (FlowConfig flowConfig : flowConfigForNode) {
2339                 if (doesFlowContainNodeConnector(flowConfig.getFlow(), nc)) {
2340                     if (flowConfig.installInHw()) {
2341                         Status status = this.installFlowEntry(flowConfig.getFlowEntry());
2342                         if (!status.isSuccess()) {
2343                             flowConfig.setStatus(status.getDescription());
2344                         } else {
2345                             flowConfig.setStatus(SUCCESS);
2346                         }
2347                         updateStaticFlowCluster = true;
2348                     }
2349                 }
2350             }
2351             if (updateStaticFlowCluster) {
2352                 refreshClusterStaticFlowsStatus(nc.getNode());
2353             }
2354             break;
2355         case CHANGED:
2356             break;
2357         default:
2358         }
2359     }
2360
2361     @Override
2362     public void containerModeUpdated(UpdateType update) {
2363         // Only default container instance reacts on this event
2364         if (!container.getName().equals(GlobalConstants.DEFAULT.toString())) {
2365             return;
2366         }
2367         switch (update) {
2368         case ADDED:
2369             this.inContainerMode = true;
2370             this.uninstallAllFlowEntries();
2371             break;
2372         case REMOVED:
2373             this.inContainerMode = false;
2374             this.reinstallAllFlowEntries();
2375             break;
2376         default:
2377         }
2378
2379         // Update our configuration DB
2380         updateStaticFlowConfigsOnContainerModeChange(update);
2381     }
2382
2383     protected abstract class FRMEvent {
2384
2385     }
2386
2387     private class NodeUpdateEvent extends FRMEvent {
2388         private final Node node;
2389         private final UpdateType update;
2390
2391         public NodeUpdateEvent(UpdateType update, Node node) {
2392             this.update = update;
2393             this.node = node;
2394         }
2395
2396         public UpdateType getUpdateType() {
2397             return update;
2398         }
2399
2400         public Node getNode() {
2401             return node;
2402         }
2403     }
2404
2405     private class ErrorReportedEvent extends FRMEvent {
2406         private final long rid;
2407         private final Node node;
2408         private final Object error;
2409
2410         public ErrorReportedEvent(long rid, Node node, Object error) {
2411             this.rid = rid;
2412             this.node = node;
2413             this.error = error;
2414         }
2415
2416         public long getRequestId() {
2417             return rid;
2418         }
2419
2420         public Object getError() {
2421             return error;
2422         }
2423
2424         public Node getNode() {
2425             return node;
2426         }
2427     }
2428
2429     /*
2430      * OSGI COMMANDS
2431      */
2432     @Override
2433     public String getHelp() {
2434         StringBuffer help = new StringBuffer();
2435         help.append("---FRM Matrix Application---\n");
2436         help.append("\t printMatrixData        - Prints the Matrix Configs\n");
2437         help.append("\t addMatrixConfig <name> <regex>\n");
2438         help.append("\t delMatrixConfig <name>\n");
2439         help.append("\t useMatrixConfig <name>\n");
2440         return help.toString();
2441     }
2442
2443     public void _printMatrixData(CommandInterpreter ci) {
2444         ci.println("Configs : ");
2445         ci.println("---------");
2446         ci.println(portGroupConfigs);
2447
2448         ci.println("Data : ");
2449         ci.println("------");
2450         ci.println(portGroupData);
2451     }
2452
2453     public void _addMatrixConfig(CommandInterpreter ci) {
2454         String name = ci.nextArgument();
2455         String regex = ci.nextArgument();
2456         addPortGroupConfig(name, regex, false);
2457     }
2458
2459     public void _delMatrixConfig(CommandInterpreter ci) {
2460         String name = ci.nextArgument();
2461         delPortGroupConfig(name);
2462     }
2463
2464     public void _useMatrixConfig(CommandInterpreter ci) {
2465         String name = ci.nextArgument();
2466         usePortGroupConfig(name);
2467     }
2468
2469     public void _arpPunt(CommandInterpreter ci) {
2470         String switchId = ci.nextArgument();
2471         long swid = HexEncode.stringToLong(switchId);
2472         Node node = NodeCreator.createOFNode(swid);
2473         installImplicitARPReplyPunt(node);
2474     }
2475
2476     public void _frmaddflow(CommandInterpreter ci) throws UnknownHostException {
2477         Node node = null;
2478         String nodeId = ci.nextArgument();
2479         if (nodeId == null) {
2480             ci.print("Node id not specified");
2481             return;
2482         }
2483         try {
2484             node = NodeCreator.createOFNode(Long.valueOf(nodeId));
2485         } catch (NumberFormatException e) {
2486             ci.print("Node id not a number");
2487             return;
2488         }
2489         ci.println(this.programmer.addFlow(node, getSampleFlow(node)));
2490     }
2491
2492     public void _frmremoveflow(CommandInterpreter ci) throws UnknownHostException {
2493         Node node = null;
2494         String nodeId = ci.nextArgument();
2495         if (nodeId == null) {
2496             ci.print("Node id not specified");
2497             return;
2498         }
2499         try {
2500             node = NodeCreator.createOFNode(Long.valueOf(nodeId));
2501         } catch (NumberFormatException e) {
2502             ci.print("Node id not a number");
2503             return;
2504         }
2505         ci.println(this.programmer.removeFlow(node, getSampleFlow(node)));
2506     }
2507
2508     private Flow getSampleFlow(Node node) throws UnknownHostException {
2509         NodeConnector port = NodeConnectorCreator.createOFNodeConnector((short) 24, node);
2510         NodeConnector oport = NodeConnectorCreator.createOFNodeConnector((short) 30, node);
2511         byte srcMac[] = { (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78, (byte) 0x9a, (byte) 0xbc };
2512         byte dstMac[] = { (byte) 0x1a, (byte) 0x2b, (byte) 0x3c, (byte) 0x4d, (byte) 0x5e, (byte) 0x6f };
2513         InetAddress srcIP = InetAddress.getByName("172.28.30.50");
2514         InetAddress dstIP = InetAddress.getByName("171.71.9.52");
2515         InetAddress ipMask = InetAddress.getByName("255.255.255.0");
2516         InetAddress ipMask2 = InetAddress.getByName("255.0.0.0");
2517         short ethertype = EtherTypes.IPv4.shortValue();
2518         short vlan = (short) 27;
2519         byte vlanPr = 3;
2520         Byte tos = 4;
2521         byte proto = IPProtocols.TCP.byteValue();
2522         short src = (short) 55000;
2523         short dst = 80;
2524
2525         /*
2526          * Create a SAL Flow aFlow
2527          */
2528         Match match = new Match();
2529         match.setField(MatchType.IN_PORT, port);
2530         match.setField(MatchType.DL_SRC, srcMac);
2531         match.setField(MatchType.DL_DST, dstMac);
2532         match.setField(MatchType.DL_TYPE, ethertype);
2533         match.setField(MatchType.DL_VLAN, vlan);
2534         match.setField(MatchType.DL_VLAN_PR, vlanPr);
2535         match.setField(MatchType.NW_SRC, srcIP, ipMask);
2536         match.setField(MatchType.NW_DST, dstIP, ipMask2);
2537         match.setField(MatchType.NW_TOS, tos);
2538         match.setField(MatchType.NW_PROTO, proto);
2539         match.setField(MatchType.TP_SRC, src);
2540         match.setField(MatchType.TP_DST, dst);
2541
2542         List<Action> actions = new ArrayList<Action>();
2543         actions.add(new Output(oport));
2544         actions.add(new PopVlan());
2545         actions.add(new Flood());
2546         actions.add(new Controller());
2547         return new Flow(match, actions);
2548     }
2549
2550     @Override
2551     public Status saveConfiguration() {
2552         return saveConfig();
2553     }
2554
2555     public void _frmNodeFlows(CommandInterpreter ci) {
2556         String nodeId = ci.nextArgument();
2557         Node node = Node.fromString(nodeId);
2558         if (node == null) {
2559             ci.println("frmNodeFlows <node> [verbose]");
2560             return;
2561         }
2562         boolean verbose = false;
2563         String verboseCheck = ci.nextArgument();
2564         if (verboseCheck != null) {
2565             verbose = verboseCheck.equals("true");
2566         }
2567