Bug 3420: Flow API should update statistics if UPDATESTATS is passed. 50/21350/1
authorShigeru Yasuda <s-yasuda@da.jp.nec.com>
Thu, 28 May 2015 16:08:37 +0000 (01:08 +0900)
committerShigeru Yasuda <s-yasuda@da.jp.nec.com>
Thu, 28 May 2015 16:08:37 +0000 (01:08 +0900)
Other changes:

  * Fixed warnings detected by findbugs.
  * Fixed incorrect javadoc doclets.
  * Refined unit tests.
    * NodeListenerTest
    * NodeConnectorListenerTest

Change-Id: Ia9bb9c33f01857594ff554e9ef967d28a835c0fc
Signed-off-by: Shigeru Yasuda <s-yasuda@da.jp.nec.com>
36 files changed:
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/MacMapCleaner.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/VTNFlowManager.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/reader/ReadDataFlowFuture.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/reader/ReadFlowFuture.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/reader/ReadSingleFlowFuture.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/remove/ClearNodeFlowsTask.java [new file with mode: 0644]
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/AddFlowStatsTask.java [new file with mode: 0644]
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/FlowStatsReader.java [new file with mode: 0644]
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/NodeFlowStatsReader.java [new file with mode: 0644]
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/StatsReaderCallback.java [new file with mode: 0644]
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/StatsReaderService.java [new file with mode: 0644]
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/StatsTimerTask.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/inventory/InventoryUpdateTask.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/inventory/NodeUpdateTask.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/inventory/VTNInventoryManager.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/util/concurrent/VTNThreadPool.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/util/flow/FlowEntryDesc.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/util/flow/FlowStatsUtils.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/util/flow/FlowUtils.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/util/flow/GetFlowStatsRpc.java [new file with mode: 0644]
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/SpecificPortFilterTest.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/config/OperationalListenerTest.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/inventory/NodeConnectorListenerTest.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/inventory/NodeListenerTest.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/inventory/VTNInventoryManagerTest.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/routing/SetPathCostTaskTest.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/routing/VTNRoutingManagerTest.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/util/concurrent/FutureCancellerTest.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/util/concurrent/VTNFutureTaskTest.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/util/concurrent/VTNThreadPoolTest.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/util/flow/FlowStatsUtilsTest.java [new file with mode: 0644]
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/util/flow/FlowUtilsTest.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/util/tx/TxSyncFutureTest.java
manager/it/ofmock/src/main/java/org/opendaylight/vtn/manager/it/ofmock/impl/FlowEntryRemover.java [new file with mode: 0644]
manager/it/ofmock/src/main/java/org/opendaylight/vtn/manager/it/ofmock/impl/FlowTableScanner.java [new file with mode: 0644]
manager/it/ofmock/src/main/java/org/opendaylight/vtn/manager/it/ofmock/impl/OfNode.java

index 4bd51b706779265653e79440dca2cba1b2621e46..aec67f9f47f40f6a0056d61082561d9ca2cf5f0b 100644 (file)
@@ -201,7 +201,6 @@ public final class MacMapCleaner
      */
     private boolean checkHost(MacVlan mvlan, NodeConnector port,
                               VNodePath mpath) {
-        short vlan = mvlan.getVlan();
         PortVlan pvlan = new PortVlan(port, mvlan.getVlan());
         MapReference ref = resourceManager.getMapReference(pvlan);
         if (ref != null && ref.getMapType() == MapType.PORT) {
index 77ba9b2b24437d564b05a12671df5736e30642ad..fed225be27fcceb820fcb3734f2514335310c72e 100644 (file)
@@ -36,12 +36,14 @@ import org.opendaylight.vtn.manager.internal.flow.add.FlowAddContext;
 import org.opendaylight.vtn.manager.internal.flow.add.PutFlowTxTask;
 import org.opendaylight.vtn.manager.internal.flow.reader.FlowCountFuture;
 import org.opendaylight.vtn.manager.internal.flow.reader.ReadFlowFuture;
+import org.opendaylight.vtn.manager.internal.flow.remove.ClearNodeFlowsTask;
 import org.opendaylight.vtn.manager.internal.flow.remove.DeleteFlowTxTask;
 import org.opendaylight.vtn.manager.internal.flow.remove.FlowRemoveContext;
 import org.opendaylight.vtn.manager.internal.flow.remove.NodeFlowRemover;
 import org.opendaylight.vtn.manager.internal.flow.remove.PortFlowRemover;
 import org.opendaylight.vtn.manager.internal.flow.remove.RemovedFlowRemover;
 import org.opendaylight.vtn.manager.internal.flow.stats.SalFlowIdResolver;
+import org.opendaylight.vtn.manager.internal.flow.stats.StatsReaderService;
 import org.opendaylight.vtn.manager.internal.flow.stats.StatsTimerTask;
 import org.opendaylight.vtn.manager.internal.inventory.VTNInventoryListener;
 import org.opendaylight.vtn.manager.internal.inventory.VtnNodeEvent;
@@ -57,7 +59,6 @@ import org.opendaylight.vtn.manager.internal.util.flow.FlowFinder;
 import org.opendaylight.vtn.manager.internal.util.flow.FlowUtils;
 import org.opendaylight.vtn.manager.internal.util.flow.VTNFlowBuilder;
 import org.opendaylight.vtn.manager.internal.util.inventory.SalNode;
-import org.opendaylight.vtn.manager.internal.util.rpc.RpcErrorCallback;
 import org.opendaylight.vtn.manager.internal.util.rpc.RpcException;
 import org.opendaylight.vtn.manager.internal.util.rpc.RpcFuture;
 import org.opendaylight.vtn.manager.internal.util.rpc.RpcUtils;
@@ -85,24 +86,19 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.Nex
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.VtnFlows;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.VtnFlowsBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.tenant.flow.info.VtnDataFlow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.inventory.rev150209.VtnOpenflowVersion;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.types.rev150209.VtnUpdateType;
 
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowAdded;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowRemoved;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowTableRef;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowUpdated;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.NodeErrorNotification;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.NodeExperimenterErrorNotification;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowInput;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowInputBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowListener;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SwitchFlowRemoved;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
 
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri;
-
 /**
  * Flow entry manager.
  */
@@ -158,6 +154,11 @@ public final class VTNFlowManager extends SalNotificationListener
     private final ConcurrentMap<FlowCookie, VtnFlowId>  removedCookies =
         new ConcurrentHashMap<>();
 
+    /**
+     * Flow statistics reader service.
+     */
+    private final StatsReaderService  statsReader;
+
     /**
      * MD-SAL transaction task to initialize internal flow containers.
      */
@@ -335,7 +336,7 @@ public final class VTNFlowManager extends SalNotificationListener
         vtnProvider = provider;
         txQueue = new TxQueueImpl("VTN Flow DS", provider);
         addCloseable(txQueue);
-        flowThread = new VTNThreadPool("VTN Flow Thread", 1, 0);
+        flowThread = new VTNThreadPool("VTN Flow Thread");
         addCloseable(flowThread);
 
         try {
@@ -347,6 +348,10 @@ public final class VTNFlowManager extends SalNotificationListener
 
             // Register MD-SAL flow ID resolver.
             addCloseable(new SalFlowIdResolver(provider, txQueue));
+
+            // Start flow statistics reader service.
+            statsReader = new StatsReaderService(provider, nsv);
+            addCloseable(statsReader);
         } catch (Exception e) {
             String msg = "Failed to initialize VTN flow service.";
             LOG.error(msg, e);
@@ -358,26 +363,12 @@ public final class VTNFlowManager extends SalNotificationListener
     /**
      * Shut down the VTN flow service.
      */
-    public synchronized void shutdown() {
-        if (flowService != null) {
-            // No more flow transaction will be accepted.
-            flowService = null;
+    public void shutdown() {
+        shutdownFlowService();
 
-            // Wait for all flow transactions to complete.
-            TimeoutCounter tc = TimeoutCounter.
-                newTimeout(SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);
-            try {
-                while (txCount > 0) {
-                    tc.await(this);
-                }
-                return;
-            } catch (TimeoutException e) {
-                if (txCount > 0) {
-                    LOG.warn("Flow transaction did not complete: {}", txCount);
-                }
-            } catch (InterruptedException e) {
-                LOG.warn("Shutdown wait has been interrupted.");
-            }
+        StatsReaderService srs = statsReader;
+        if (srs != null) {
+            srs.shutdown();
         }
     }
 
@@ -426,13 +417,29 @@ public final class VTNFlowManager extends SalNotificationListener
     }
 
     /**
-     * Return the MD-SAL flow service.
-     *
-     * @return  The MD-SAL flow service.
-     *          {@code null} if the flow service is already closed.
+     * Shut down the VTN flow service.
      */
-    private synchronized SalFlowService getFlowService() {
-        return flowService;
+    private synchronized void shutdownFlowService() {
+        if (flowService != null) {
+            // No more flow transaction will be accepted.
+            flowService = null;
+
+            // Wait for all flow transactions to complete.
+            TimeoutCounter tc = TimeoutCounter.
+                newTimeout(SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);
+            try {
+                while (txCount > 0) {
+                    tc.await(this);
+                }
+                return;
+            } catch (TimeoutException e) {
+                if (txCount > 0) {
+                    LOG.warn("Flow transaction did not complete: {}", txCount);
+                }
+            } catch (InterruptedException e) {
+                LOG.warn("Shutdown wait has been interrupted.");
+            }
+        }
     }
 
     /**
@@ -475,6 +482,40 @@ public final class VTNFlowManager extends SalNotificationListener
         }
     }
 
+    /**
+     * Clear the flow table in the specified switch.
+     *
+     * @param snode  A {@link SalNode} instance which specifies the target
+     *               switch.
+     * @param ofver  A {@link VtnOpenflowVersion} instance which specifies the
+     *               OpenFlow protocol version. {@code null} means the protocol
+     *               version is not yet determined.
+     */
+    private synchronized void clearFlowTable(SalNode snode,
+                                             VtnOpenflowVersion ofver) {
+        SalFlowService sfs = flowService;
+        if (sfs == null) {
+            return;
+        }
+
+        // Increment the transaction counter.
+        assert txCount >= 0;
+        txCount++;
+
+        // Start ClearNodeFlowsTask.
+        ClearNodeFlowsTask task = new ClearNodeFlowsTask(
+            vtnProvider, sfs, statsReader, snode, ofver);
+        VTNFuture<Void> f = task.getFuture();
+        if (!flowThread.executeTask(task)) {
+            f.cancel(false);
+        }
+
+        // Add callback which will decrement the transaction counter when
+        // the future completes.
+        TxCounterCallback<Void> cb = new TxCounterCallback<>();
+        Futures.addCallback(f, cb);
+    }
+
     /**
      * Search for the VTN data flow associated with the given ID.
      *
@@ -499,7 +540,7 @@ public final class VTNFlowManager extends SalNotificationListener
     // AutoCloseable
 
     /**
-     * Close the internal packet service.
+     * Close the VTN flow manager service.
      */
     @Override
     public void close() {
@@ -553,29 +594,8 @@ public final class VTNFlowManager extends SalNotificationListener
         VtnUpdateType type = ev.getUpdateType();
         if (type == VtnUpdateType.CREATED) {
             // Clear the flow table in the new switch.
-            SalFlowService sfs = getFlowService();
-            if (sfs != null) {
-                SalNode snode = ev.getSalNode();
-                LOG.debug("{}: Clean up the flow table.", snode);
-                Short tid = Short.valueOf(FlowUtils.TABLE_ID);
-                FlowTableRef tref = new FlowTableRef(
-                    snode.getFlowTableIdentifier(tid));
-                StringBuilder sb = new StringBuilder("clean-up:").
-                    append(snode);
-                RemoveFlowInput input = new RemoveFlowInputBuilder().
-                    setNode(snode.getNodeRef()).
-                    setFlowTable(tref).
-                    setTransactionUri(new Uri(sb.toString())).
-                    setTableId(tid).
-                    setStrict(false).
-                    setBarrier(true).
-                    build();
-                Future<RpcResult<RemoveFlowOutput>> f = sfs.removeFlow(input);
-                RpcErrorCallback<RemoveFlowOutput> cb = new RpcErrorCallback<>(
-                    LOG, "clean-up-flow",
-                    "Failed to clean up the flow table: %s", snode);
-                vtnProvider.setCallback(f, cb);
-            }
+            VtnOpenflowVersion ofver = ev.getVtnNode().getOpenflowVersion();
+            clearFlowTable(ev.getSalNode(), ofver);
         }
 
         // Uninstall VTN flows affected by the node.
@@ -607,7 +627,8 @@ public final class VTNFlowManager extends SalNotificationListener
         GetDataFlowInput input) {
         TxContext ctx = vtnProvider.newTxContext();
         try {
-            ReadFlowFuture f = ReadFlowFuture.create(ctx, txQueue, input);
+            ReadFlowFuture f =
+                ReadFlowFuture.create(ctx, txQueue, statsReader, input);
             return new RpcFuture<List<DataFlowInfo>, GetDataFlowOutput>(f, f);
         } catch (VTNException | RuntimeException e) {
             ctx.cancelTransaction();
index b1978bbb272057f278b33d9667980dbb40e90117..7f9304a4be40d75cc181eca408fd48f7cf47000b 100644 (file)
@@ -25,6 +25,8 @@ import org.opendaylight.vtn.manager.VTNException;
 import org.opendaylight.vtn.manager.internal.TxContext;
 import org.opendaylight.vtn.manager.internal.TxQueue;
 import org.opendaylight.vtn.manager.internal.cluster.MacVlan;
+import org.opendaylight.vtn.manager.internal.flow.stats.FlowStatsReader;
+import org.opendaylight.vtn.manager.internal.flow.stats.StatsReaderService;
 import org.opendaylight.vtn.manager.internal.util.flow.FlowCache;
 import org.opendaylight.vtn.manager.internal.util.flow.FlowUtils;
 import org.opendaylight.vtn.manager.internal.util.inventory.SalNode;
@@ -154,18 +156,13 @@ public final class ReadDataFlowFuture extends ReadFlowFuture
          */
         @Override
         public void onSuccess(List<Optional<VtnDataFlow>> result) {
-            List<DataFlowInfo> flows = new ArrayList<>();
+            int size = result.size();
+            List<DataFlowInfo> flows = new ArrayList<>(size);
             int index = 0;
+            FlowStatsReader stReader = getFlowStatsReader(result.size());
             for (Optional<VtnDataFlow> opt: result) {
                 if (opt.isPresent()) {
-                    FlowCache fc = new FlowCache(opt.get());
-                    if (select(fc)) {
-                        DataFlowInfo df = toDataFlowInfo(fc, null);
-                        if (df == null) {
-                            return;
-                        }
-                        flows.add(df);
-                    }
+                    addDataFlowInfo(flows, opt.get(), stReader);
                 } else {
                     // This should never happen.
                     Logger logger = LoggerFactory.
@@ -198,15 +195,17 @@ public final class ReadDataFlowFuture extends ReadFlowFuture
      * @param ctx    A {@link TxContext} instance.
      * @param txq    A {@link TxQueue} instance used to update the MD-SAL
      *               datastore.
+     * @param srs    The flow statistics reader service.
      * @param input  Input of the RPC call.
      * @throws VTNException  An error occurred.
      */
     public ReadDataFlowFuture(TxContext ctx, TxQueue txq,
-                              GetDataFlowInput input) throws VTNException {
-        super(ctx, txq, input);
+                              StatsReaderService srs, GetDataFlowInput input)
+        throws VTNException {
+        super(ctx, txq, srs, input);
 
         // Determine flow index to be used.
-        if (readIndex(input) == null) {
+        if (!isDone() && readIndex(input) == null) {
             // Scan all the data flows present in the VTN.
             ReadTransaction rtx = getTxContext().getTransaction();
             LogicalDatastoreType oper = LogicalDatastoreType.OPERATIONAL;
@@ -224,41 +223,68 @@ public final class ReadDataFlowFuture extends ReadFlowFuture
      *          index read operation. {@code null} if no index should be used.
      */
     private IndexCallback<?> readIndex(GetDataFlowInput input) {
-        if (!isDone()) {
-            MacVlan src = getSourceHost();
-            if (src != null) {
-                // Use source host inedx.
-                SourceHostFlowsKey hostId = src.getSourceHostFlowsKey();
-                clearSourceHost();
-                InstanceIdentifier<SourceHostFlows> path =
-                    FlowUtils.getIdentifier(input.getTenantName(), hostId);
-                return new IndexCallback<SourceHostFlows>(path);
-            }
+        MacVlan src = getSourceHost();
+        if (src != null) {
+            // Use source host inedx.
+            SourceHostFlowsKey hostId = src.getSourceHostFlowsKey();
+            clearSourceHost();
+            InstanceIdentifier<SourceHostFlows> path =
+                FlowUtils.getIdentifier(input.getTenantName(), hostId);
+            return new IndexCallback<SourceHostFlows>(path);
+        }
 
-            SalPort sport = getFlowPort();
-            if (sport != null) {
-                // Use switch port index.
-                clearFlowPort();
-                InstanceIdentifier<PortFlows> path =
-                    FlowUtils.getIdentifier(input.getTenantName(),
-                                            sport.getNodeConnectorId());
-                return new IndexCallback<PortFlows>(path);
-            }
+        SalPort sport = getFlowPort();
+        if (sport != null) {
+            // Use switch port index.
+            clearFlowPort();
+            InstanceIdentifier<PortFlows> path = FlowUtils.getIdentifier(
+                input.getTenantName(), sport.getNodeConnectorId());
+            return new IndexCallback<PortFlows>(path);
+        }
 
-            SalNode snode = getFlowNode();
-            if (snode != null) {
-                // Use switch index.
-                clearFlowNode();
-                InstanceIdentifier<NodeFlows> path =
-                    FlowUtils.getIdentifier(input.getTenantName(),
-                                            snode.getNodeId());
-                return new IndexCallback<NodeFlows>(path);
-            }
+        SalNode snode = getFlowNode();
+        if (snode != null) {
+            // Use switch index.
+            clearFlowNode();
+            InstanceIdentifier<NodeFlows> path = FlowUtils.getIdentifier(
+                input.getTenantName(), snode.getNodeId());
+            return new IndexCallback<NodeFlows>(path);
         }
 
         return null;
     }
 
+    /**
+     * Return a flow statistics reader.
+     *
+     * @param count  The estimated number of flow entries present in switches.
+     * @return  A {@link FlowStatsReader} service.
+     */
+    private FlowStatsReader getFlowStatsReader(int count) {
+        StatsReaderService srs = getStatsReaderService();
+        return FlowStatsReader.getInstance(srs, count);
+    }
+
+    /**
+     * Convert the given VTN data flow into a {@link DataFlowInfo} instance,
+     * and add it to the given list.
+     *
+     * @param list      A list to store converted instances.
+     * @param vdf       A {@link VtnDataFlow} instance.
+     * @param stReader  A {@link FlowStatsReader} instance used to update
+     *                  flow statistics.
+     */
+    private void addDataFlowInfo(List<DataFlowInfo> list, VtnDataFlow vdf,
+                                 FlowStatsReader stReader) {
+        FlowCache fc = new FlowCache(vdf);
+        if (select(fc)) {
+            DataFlowInfo df = toDataFlowInfo(fc, stReader.get(fc));
+            if (df != null) {
+                list.add(df);
+            }
+        }
+    }
+
     // FutureCallback
 
     /**
@@ -273,16 +299,11 @@ public final class ReadDataFlowFuture extends ReadFlowFuture
             // Select data flows which meet the condition.
             List<VtnDataFlow> flows = result.get().getVtnDataFlow();
             if (flows != null) {
-                List<DataFlowInfo> list = new ArrayList<>();
+                int size = flows.size();
+                FlowStatsReader stReader = getFlowStatsReader(size);
+                List<DataFlowInfo> list = new ArrayList<>(size);
                 for (VtnDataFlow vdf: flows) {
-                    FlowCache fc = new FlowCache(vdf);
-                    if (select(fc)) {
-                        DataFlowInfo df = toDataFlowInfo(fc, null);
-                        if (df == null) {
-                            return;
-                        }
-                        list.add(df);
-                    }
+                    addDataFlowInfo(list, vdf, stReader);
                 }
                 set(list);
                 return;
index 75d78a0cab648731f4864bf7fe79f1c43f32be7b..fd58181d9cf442b981a7d4410ea30ab81522a8a0 100644 (file)
@@ -25,7 +25,10 @@ import org.opendaylight.vtn.manager.internal.L2Host;
 import org.opendaylight.vtn.manager.internal.TxContext;
 import org.opendaylight.vtn.manager.internal.TxQueue;
 import org.opendaylight.vtn.manager.internal.cluster.MacVlan;
+import org.opendaylight.vtn.manager.internal.flow.stats.AddFlowStatsTask;
+import org.opendaylight.vtn.manager.internal.flow.stats.StatsReaderService;
 import org.opendaylight.vtn.manager.internal.util.MiscUtils;
+import org.opendaylight.vtn.manager.internal.util.concurrent.VTNFuture;
 import org.opendaylight.vtn.manager.internal.util.flow.FlowCache;
 import org.opendaylight.vtn.manager.internal.util.inventory.InventoryReader;
 import org.opendaylight.vtn.manager.internal.util.inventory.SalNode;
@@ -43,6 +46,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.flow.rev150410.get.data
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.flow.rev150410.vtn.data.flow.info.AveragedDataFlowStats;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.flow.rev150410.vtn.data.flow.info.AveragedDataFlowStatsBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.flow.rev150410.vtn.data.flow.info.DataFlowStatsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.tenant.flow.info.VtnDataFlow;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.FlowStatsHistory;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecord;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.types.rev150209.VlanHost;
@@ -61,18 +65,29 @@ public abstract class ReadFlowFuture
     /**
      * Default value for the averaged statistics interval.
      */
-    private static final long  DEFAULT_AVERAGE_INTERVAL = 10;
+    private static final long  DEFAULT_AVERAGE_INTERVAL = 10L;
 
     /**
      * The number of milliseconds in a second.
      */
     private static final double  MILLISEC = 1000D;
 
+    /**
+     * The number of milliseconds to wait for completion of transaction for
+     * getting flow statistics.
+     */
+    private static final long  STATS_READ_TIMEOUT = 10000L;
+
     /**
      * The MD-SAL transaction queue used to update the MD-SAL datastore.
      */
     private final TxQueue  txQueue;
 
+    /**
+     * Flow statistics reader service.
+     */
+    private final StatsReaderService  statsReader;
+
     /**
      * A {@link SalNode} instance which specifies the physical switch.
      */
@@ -105,11 +120,13 @@ public abstract class ReadFlowFuture
      * @param ctx    A {@link TxContext} instance.
      * @param txq    A {@link TxQueue} instance used to update the MD-SAL
      *               datastore.
+     * @param srs    The flow statistics reader service.
      * @param input  Input of the RPC call.
      * @return  A {@link ReadFlowFuture} instance.
      * @throws VTNException  An error occurred.
      */
     public static final ReadFlowFuture create(TxContext ctx, TxQueue txq,
+                                              StatsReaderService srs,
                                               GetDataFlowInput input)
         throws VTNException {
         if (input == null) {
@@ -117,8 +134,8 @@ public abstract class ReadFlowFuture
         }
 
         return (input.getFlowId() == null)
-            ? new ReadDataFlowFuture(ctx, txq, input)
-            : new ReadSingleFlowFuture(ctx, txq, input);
+            ? new ReadDataFlowFuture(ctx, txq, srs, input)
+            : new ReadSingleFlowFuture(ctx, txq, srs, input);
     }
 
     /**
@@ -127,11 +144,13 @@ public abstract class ReadFlowFuture
      * @param ctx    A {@link TxContext} instance.
      * @param txq    A {@link TxQueue} instance used to update the MD-SAL
      *               datastore.
+     * @param srs    The flow statistics reader service.
      * @param input  Input of the RPC call.
      * @throws VTNException  An error occurred.
      */
     protected ReadFlowFuture(TxContext ctx, TxQueue txq,
-                             GetDataFlowInput input) throws VTNException {
+                             StatsReaderService srs, GetDataFlowInput input)
+        throws VTNException {
         super(ctx, input.getTenantName());
         txQueue = txq;
 
@@ -142,6 +161,9 @@ public abstract class ReadFlowFuture
         }
         flowMode = mode;
 
+        statsReader = (mode == DataFlowMode.UPDATESTATS)
+            ? srs : null;
+
         Integer interval = input.getAverageInterval();
         long ival = (interval == null) ? 0 : interval.longValue();
         if (ival <= 0) {
@@ -152,6 +174,17 @@ public abstract class ReadFlowFuture
         setCondition(ctx, input);
     }
 
+    /**
+     * Return the flow statistics reader service.
+     *
+     * @return  A {@link StatsReaderService} instance if flow statistics
+     *          information needs to be fetched from switches.
+     *          Otherwise {@code null}.
+     */
+    protected final StatsReaderService getStatsReaderService() {
+        return statsReader;
+    }
+
     /**
      * Return a {@link SalNode} instance corresponding to the physical switch
      * specified by the RPC input.
@@ -248,14 +281,14 @@ public abstract class ReadFlowFuture
      * </p>
      *
      * @param fc       A {@link FlowCache} instance.
-     * @param current  A {@link FlowStatsRecord} that contains the current
-     *                 statistics information. If {@code null}, only flow
+     * @param current  A future which will return the current flow statistics
+     *                 for the given data flow. If {@code null}, only flow
      *                 statistics cached in {@code fc} are used.
      * @return  A {@link DataFlowInfo} instance on success.
      *          {@code null} on failure.
      */
-    protected final DataFlowInfo toDataFlowInfo(FlowCache fc,
-                                                FlowStatsRecord current) {
+    protected final DataFlowInfo toDataFlowInfo(
+        FlowCache fc, VTNFuture<FlowStatsRecord> current) {
         try {
             InventoryReader reader = getTxContext().getInventoryReader();
             DataFlowInfoBuilder builder =
@@ -333,39 +366,85 @@ public abstract class ReadFlowFuture
         }
     }
 
+    /**
+     * Return a list of flow statistics history records in the given VTN
+     * data flow.
+     *
+     * @param vdf  The target VTN data flow.
+     * @return  A list of {@link FlowStatsRecord} instance.
+     */
+    private List<FlowStatsRecord> getFlowStatsRecords(VtnDataFlow vdf) {
+        FlowStatsHistory history = vdf.getFlowStatsHistory();
+        if (history != null) {
+            List<FlowStatsRecord> list = history.getFlowStatsRecord();
+            if (list != null) {
+                return list;
+            }
+        }
+
+        // No statistics information is available.
+        return Collections.<FlowStatsRecord>emptyList();
+    }
+
+    /**
+     * Return the flow statistics record from the given future.
+     *
+     * @param vdf      The target VTN data flow.
+     * @param current  A future which will return the current flow statistics
+     *                 for the given data flow. Note that {@code null} is
+     *                 returned if {@code null} is specified.
+     * @return  A {@link FlowStatsRecord} instance returned by the given
+     *          future. {@code null} if the flow statistics is not available.
+     */
+    private FlowStatsRecord getFlowStatsRecord(
+        VtnDataFlow vdf, VTNFuture<FlowStatsRecord> current) {
+        if (current != null) {
+            try {
+                return current.checkedGet(STATS_READ_TIMEOUT,
+                                          TimeUnit.MILLISECONDS);
+            } catch (VTNException | RuntimeException e) {
+                Logger logger = LoggerFactory.getLogger(getClass());
+                String msg = "Flow statistics is unavailable: flowId={}" +
+                    vdf.getFlowId().getValue();
+                logger.warn(msg, e);
+            }
+        }
+
+        return null;
+    }
+
     /**
      * Set the flow statistics information into the given data flow info
      * builder.
      *
      * @param builder  A {@link DataFlowInfoBuilder} instance.
      * @param fc       A {@link FlowCache} instance.
-     * @param current  A {@link FlowStatsRecord} that contains the current
-     *                 statistics information. If {@code null}, only flow
+     * @param current  A future which will return the current flow statistics
+     *                 for the given data flow. If {@code null}, only flow
      *                 statistics cached in {@code fc} are used.
      */
     private void setStatistics(DataFlowInfoBuilder builder, FlowCache fc,
-                               FlowStatsRecord current) {
-        FlowStatsHistory history = fc.getDataFlow().getFlowStatsHistory();
-        if (history == null) {
-            // No statistics information is available.
-            return;
-        }
-
-        List<FlowStatsRecord> list = history.getFlowStatsRecord();
-        if (list == null || list.isEmpty()) {
-            // No statistics information is available.
-            return;
-        }
-
+                               VTNFuture<FlowStatsRecord> current) {
         // Sort history records in ascending order of the system time.
+        VtnDataFlow vdf = fc.getDataFlow();
+        List<FlowStatsRecord> list = getFlowStatsRecords(vdf);
         TreeMap<Long, FlowStatsRecord> map = new TreeMap<>();
         for (FlowStatsRecord fsr: list) {
             map.put(fsr.getTime(), fsr);
         }
 
         // Add the current statistics if specified.
-        if (current != null) {
-            map.put(current.getTime(), current);
+        FlowStatsRecord cfsr = getFlowStatsRecord(vdf, current);
+        if (cfsr != null) {
+            map.put(cfsr.getTime(), cfsr);
+            AddFlowStatsTask task = new AddFlowStatsTask(
+                getTenantName(), vdf.getFlowId(), cfsr);
+            txQueue.post(task);
+        }
+
+        if (map.isEmpty()) {
+            // No statistics information is available.
+            return;
         }
 
         FlowStatsRecord latest = map.lastEntry().getValue();
index 1178343aa7b804bb5ecb8fe2ac1add6a24c8a626..0b774692c678e1df96561bf6236417fc76d867fc 100644 (file)
@@ -19,6 +19,8 @@ import org.opendaylight.vtn.manager.VTNException;
 
 import org.opendaylight.vtn.manager.internal.TxContext;
 import org.opendaylight.vtn.manager.internal.TxQueue;
+import org.opendaylight.vtn.manager.internal.flow.stats.StatsReaderService;
+import org.opendaylight.vtn.manager.internal.util.concurrent.VTNFuture;
 import org.opendaylight.vtn.manager.internal.util.flow.FlowCache;
 import org.opendaylight.vtn.manager.internal.util.flow.FlowUtils;
 
@@ -30,6 +32,8 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.flow.rev150410.GetDataFlowInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.flow.rev150410.get.data.flow.output.DataFlowInfo;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.tenant.flow.info.VtnDataFlow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.VtnFlowEntry;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecord;
 
 /**
  * {@code ReadSingleFlowFuture} describes a future associated with the task
@@ -43,12 +47,14 @@ public final class ReadSingleFlowFuture extends ReadFlowFuture
      * @param ctx    A {@link TxContext} instance.
      * @param txq    A {@link TxQueue} instance used to update the MD-SAL
      *               datastore.
+     * @param srs    The flow statistics reader service.
      * @param input  Input of the RPC call.
      * @throws VTNException  An error occurred.
      */
     public ReadSingleFlowFuture(TxContext ctx, TxQueue txq,
-                                GetDataFlowInput input) throws VTNException {
-        super(ctx, txq, input);
+                                StatsReaderService srs, GetDataFlowInput input)
+        throws VTNException {
+        super(ctx, txq, srs, input);
 
         if (!isDone()) {
             InstanceIdentifier<VtnDataFlow> path = FlowUtils.
@@ -59,6 +65,28 @@ public final class ReadSingleFlowFuture extends ReadFlowFuture
         }
     }
 
+    /**
+     * Return a future associated with a task which reads flow statistics for
+     * the target VTN data flow.
+     *
+     * @param fc  The target VTN data flow.
+     * @return  A future which will return flow statistics for the target
+     *          VTN data flow. Note that {@code null} is returned if flow
+     *          statistics does not need to be read.
+     */
+    private VTNFuture<FlowStatsRecord> readStats(FlowCache fc) {
+        StatsReaderService srs = getStatsReaderService();
+        if (srs != null) {
+            // Try to update statistics for the ingress flow entry.
+            VtnFlowEntry ingress = fc.getIngressFlow();
+            if (ingress != null) {
+                return srs.get(ingress);
+            }
+        }
+
+        return null;
+    }
+
     // FutureCallback
 
     /**
@@ -73,7 +101,7 @@ public final class ReadSingleFlowFuture extends ReadFlowFuture
             // Ensure that this data flow meets the condition.
             FlowCache fc = new FlowCache(result.get());
             if (select(fc)) {
-                DataFlowInfo df = toDataFlowInfo(fc, null);
+                DataFlowInfo df = toDataFlowInfo(fc, readStats(fc));
                 if (df != null) {
                     setResult(Collections.singletonList(df));
                 }
diff --git a/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/remove/ClearNodeFlowsTask.java b/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/remove/ClearNodeFlowsTask.java
new file mode 100644 (file)
index 0000000..723a2e6
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2015 NEC Corporation
+ * All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.vtn.manager.internal.flow.remove;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.vtn.manager.VTNException;
+
+import org.opendaylight.vtn.manager.internal.VTNConfig;
+import org.opendaylight.vtn.manager.internal.VTNManagerProvider;
+import org.opendaylight.vtn.manager.internal.flow.stats.StatsReaderCallback;
+import org.opendaylight.vtn.manager.internal.flow.stats.StatsReaderService;
+import org.opendaylight.vtn.manager.internal.util.concurrent.SettableVTNFuture;
+import org.opendaylight.vtn.manager.internal.util.concurrent.VTNFuture;
+import org.opendaylight.vtn.manager.internal.util.flow.FlowUtils;
+import org.opendaylight.vtn.manager.internal.util.flow.RemoveFlowRpc;
+import org.opendaylight.vtn.manager.internal.util.inventory.SalNode;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.flow.rev150410.VtnFlowId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.inventory.rev150209.VtnOpenflowVersion;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowTableRef;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowInputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowAndStatisticsMap;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri;
+
+/**
+ * {@code ClearNodeFlowsTask} describes a task which uninstalls VTN flow
+ * entries present in the given switch.
+ *
+ * <p>
+ *   This task will remove only flow entries installed by the VTN Manager, and
+ *   will keep flow entries installed by another component.
+ * </p>
+ */
+public final class ClearNodeFlowsTask
+    implements Runnable, StatsReaderCallback {
+    /**
+     * Logger instance.
+     */
+    private static final Logger  LOG =
+        LoggerFactory.getLogger(ClearNodeFlowsTask.class);
+
+    /**
+     * The timeout, in seconds, of flow statistics read transaction.
+     */
+    private static final long  STATS_READ_TIMEOUT = 20;
+
+    /**
+     * VTN Manager provider service.
+     */
+    private final VTNManagerProvider  vtnProvider;
+
+    /**
+     * The MD-SAL flow service.
+     */
+    private final SalFlowService  flowService;
+
+    /**
+     * The target switch.
+     */
+    private final SalNode  targetNode;
+
+    /**
+     * The OpenFlow protocol version used to communicate with the target
+     * switch.
+     */
+    private final VtnOpenflowVersion  ofVersion;
+
+    /**
+     * A future associated with this task.
+     */
+    private final SettableVTNFuture<Void>  taskFuture =
+        new SettableVTNFuture<>();
+
+    /**
+     * A future used to wait for RPC invocations.
+     */
+    private final SettableVTNFuture<Void>  rpcFuture =
+        new SettableVTNFuture<>();
+
+    /**
+     * A list of RPC invocations which uninstalls VTN flows.
+     */
+    private final List<RemoveFlowRpc>  rpcList;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param provider  VTN Manager provider service.
+     * @param sfs       The MD-SAL flow service.
+     * @param srs       Flow statistics reader service.
+     * @param snode     A {@link SalNode} instance which specifies the target
+     *                  switch.
+     * @param ofver     A {@link VtnOpenflowVersion} instance which indicates
+     *                  the OpenFlow protocol version.
+     */
+    public ClearNodeFlowsTask(VTNManagerProvider provider, SalFlowService sfs,
+                              StatsReaderService srs, SalNode snode,
+                              VtnOpenflowVersion ofver) {
+        vtnProvider = provider;
+        flowService = sfs;
+        targetNode = snode;
+        ofVersion = ofver;
+
+        if (ofver == VtnOpenflowVersion.OF13) {
+            LOG.debug("Remove all VTN flows by cookie mask: {}", snode);
+            RemoveFlowInput input = FlowUtils.createRemoveFlowInput(snode);
+            rpcList = Collections.singletonList(new RemoveFlowRpc(sfs, input));
+            rpcFuture.set(null);
+        } else {
+            // Read flow entries in the given switch.
+            LOG.debug("Scanning flow table to remove VTN flows: {}", snode);
+            rpcList = new ArrayList<>();
+            if (!srs.start(snode, this)) {
+                transactionCanceled();
+            }
+        }
+    }
+
+    /**
+     * Return a future associated with this task.
+     *
+     * @return  A {@link VTNFuture} instance associated with this task.
+     */
+    public VTNFuture<Void> getFuture() {
+        return taskFuture;
+    }
+
+    // Runnable
+
+    /**
+     * Wait for all VTN flows in the target switch to be uninstalled.
+     */
+    @Override
+    public void run() {
+        // Wait for remove-flow request to be sent to the target switch.
+        try {
+            rpcFuture.checkedGet(STATS_READ_TIMEOUT, TimeUnit.SECONDS);
+        } catch (VTNException e) {
+            if (rpcFuture.cancel(false)) {
+                // This should never happen because the flow statistics read
+                // transaction will be timed out in 10 seconds.
+                LOG.error("Failed to determine flow entries to be removed: " +
+                          targetNode, e);
+                return;
+            }
+        }
+
+        // Wait for completion of RPC tasks.
+        TimeUnit nano = TimeUnit.NANOSECONDS;
+        TimeUnit milli = TimeUnit.MILLISECONDS;
+        VTNConfig vcfg = vtnProvider.getVTNConfig();
+        int msec = vcfg.getBulkFlowModTimeout();
+        long timeout = milli.toNanos((long)msec);
+        long deadline = System.nanoTime() + timeout;
+
+        VTNException firstError = null;
+        for (RemoveFlowRpc rpc: rpcList) {
+            try {
+                rpc.getResult(timeout, nano, LOG);
+            } catch (VTNException e) {
+                if (firstError == null) {
+                    firstError = e;
+                }
+                continue;
+            }
+            LOG.trace("remove-flow has completed successfully: input={}",
+                      rpc.getInput());
+
+            timeout = deadline - System.nanoTime();
+            if (timeout <= 0) {
+                // Wait one more millisecond.
+                timeout = milli.toNanos(1L);
+            }
+        }
+
+        if (firstError == null) {
+            LOG.debug("VTN flows has been cleared: {}", targetNode);
+        } else {
+            LOG.error("Failed to remove VTN flows in " + targetNode,
+                      firstError);
+        }
+    }
+
+    // StatsReaderCallback
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void flowStatsReceived(NodeId node, FlowAndStatisticsMap fstats) {
+        assert node.equals(targetNode.getNodeId());
+
+        FlowCookie cookie = fstats.getCookie();
+        VtnFlowId fid = FlowUtils.getVtnFlowId(cookie);
+        if (fid != null) {
+            LOG.trace("Remove VTN flow entry: node={}, flow={}",
+                      node.getValue(), fstats);
+
+            StringBuilder builder = new StringBuilder("clear-node-flows:").
+                append(fid.getValue());
+            Uri uri = new Uri(builder.toString());
+            RemoveFlowInput input =
+                FlowUtils.createRemoveFlowInput(targetNode, fstats, uri);
+            rpcList.add(new RemoveFlowRpc(flowService, input));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void transactionCompleted() {
+        LOG.debug("All the flow entries have been scanned: {}", targetNode);
+        rpcFuture.set(null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void transactionCanceled() {
+        LOG.warn("Failed to read VTN flows: {}", targetNode);
+
+        if (!rpcFuture.isDone()) {
+            LOG.warn("All flow entries in {} will be removed.", targetNode);
+
+            Short tid = Short.valueOf(FlowUtils.TABLE_ID);
+            FlowTableRef tref =
+                new FlowTableRef(targetNode.getFlowTableIdentifier(tid));
+            StringBuilder sb = new StringBuilder("clean-up:").
+                append(targetNode);
+            RemoveFlowInput input = new RemoveFlowInputBuilder().
+                setNode(targetNode.getNodeRef()).
+                setFlowTable(tref).
+                setTransactionUri(new Uri(sb.toString())).
+                setTableId(tid).
+                setStrict(false).
+                setBarrier(true).
+                build();
+
+            rpcList.add(new RemoveFlowRpc(flowService, input));
+            rpcFuture.set(null);
+        }
+    }
+}
diff --git a/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/AddFlowStatsTask.java b/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/AddFlowStatsTask.java
new file mode 100644 (file)
index 0000000..e0d2501
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2015 NEC Corporation
+ * All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.vtn.manager.internal.flow.stats;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+
+import org.opendaylight.vtn.manager.VTNException;
+
+import org.opendaylight.vtn.manager.internal.TxContext;
+import org.opendaylight.vtn.manager.internal.VTNManagerProvider;
+import org.opendaylight.vtn.manager.internal.util.DataStoreUtils;
+import org.opendaylight.vtn.manager.internal.util.flow.FlowStatsUtils;
+import org.opendaylight.vtn.manager.internal.util.flow.FlowUtils;
+import org.opendaylight.vtn.manager.internal.util.tx.AbstractTxTask;
+
+import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.flow.rev150410.VtnFlowId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.tenant.flow.info.VtnDataFlow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.FlowStatsHistory;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecord;
+
+/**
+ * {@code AddFlowStatsTask} describes the MD-SAL datastore transaction task
+ * that adds the given flow statistics history record to the VTN data flow.
+ *
+ * <p>
+ *   This task returns {@link Boolean#TRUE} if the given flow statistics was
+ *   added successfully.
+ * </p>
+ */
+public final class AddFlowStatsTask extends AbstractTxTask<Boolean> {
+    /**
+     * Logger instance.
+     */
+    public static final Logger  LOG =
+        LoggerFactory.getLogger(AddFlowStatsTask.class);
+
+    /**
+     * The name of the target VTN.
+     */
+    private final String  tenantName;
+
+    /**
+     * The identifier of the target VTN data flow.
+     */
+    private final VtnFlowId  flowId;
+
+    /**
+     * The flow statistics record to be added.
+     */
+    private final FlowStatsRecord  statsRecord;
+
+    /**
+     * Construct a new instance.
+     *
+     * @param tname  The name of the target VTN.
+     * @param fid    The identifier of the target VTN data flow.
+     * @param fsr    The flow statistics record to be added.
+     */
+    public AddFlowStatsTask(String tname, VtnFlowId fid, FlowStatsRecord fsr) {
+        tenantName = tname;
+        flowId = fid;
+        statsRecord = fsr;
+    }
+
+    /**
+     * Update the flow statistics history.
+     *
+     * @param ctx  A runtime context for MD-SAL datastore transaction task.
+     * @return  {@link Boolean#TRUE} if the flow statistics history was
+     *          updated. {@link Boolean#FALSE} if not updated.
+     * @throws VTNException  An error occurred.
+     */
+    private Boolean update(TxContext ctx) throws VTNException {
+        // Read the current statistics history.
+        InstanceIdentifier<FlowStatsHistory> path = FlowUtils.
+            getIdentifier(tenantName, flowId).
+            child(FlowStatsHistory.class);
+        LogicalDatastoreType oper = LogicalDatastoreType.OPERATIONAL;
+        ReadWriteTransaction tx = ctx.getReadWriteTransaction();
+        Optional<FlowStatsHistory> opt = DataStoreUtils.read(tx, oper, path);
+        FlowStatsHistory history;
+        if (opt.isPresent()) {
+            history = opt.get();
+        } else {
+            // Check to see if the target VTN data flow is present.
+            InstanceIdentifier<VtnDataFlow> fpath =
+                path.firstIdentifierOf(VtnDataFlow.class);
+            Optional<VtnDataFlow> fopt = DataStoreUtils.read(tx, oper, fpath);
+            if (!fopt.isPresent()) {
+                // The target VTN data flow is no longer present.
+                return Boolean.FALSE;
+            }
+            history = null;
+        }
+
+        // Update the statistics history.
+        FlowStatsHistory newHist =
+            FlowStatsUtils.addNonPeriodic(history, statsRecord);
+        if (newHist != null) {
+            tx.put(oper, path, newHist, false);
+            return Boolean.TRUE;
+        }
+
+        return Boolean.FALSE;
+    }
+
+    // AbstractTxTask
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Boolean execute(TxContext ctx) throws VTNException {
+        Boolean ret = update(ctx);
+        if (ret == null) {
+            ctx.cancelTransaction();
+        }
+
+        return ret;
+    }
+
+    // TxTask
+
+    /**
+     * Invoked when the task has been completed successfully.
+     *
+     * @param provider  VTN Manager provider service.
+     * @param result    The result of this task.
+     */
+    @Override
+    public void onSuccess(VTNManagerProvider provider, Boolean result) {
+        if (Boolean.TRUE.equals(result)) {
+            LOG.debug("Flow statistics has been added: id={}, time={}",
+                      flowId.getValue(), statsRecord.getTime());
+        } else if (LOG.isTraceEnabled()) {
+            LOG.trace("Flow statistics was ignored: id={}, record={}",
+                      flowId.getValue(), statsRecord);
+        }
+    }
+
+    /**
+     * Invoked when the task has failed.
+     *
+     * @param provider  VTN Manager provider service.
+     * @param t         A {@link Throwable} thrown by the task.
+     */
+    @Override
+    public void onFailure(VTNManagerProvider provider, Throwable t) {
+        StringBuilder builder =
+            new StringBuilder("Failed to add flow statistics: id=");
+        String msg = builder.append(flowId.getValue()).
+            append(", record=").append(statsRecord).toString();
+        LOG.error(msg, t);
+    }
+}
diff --git a/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/FlowStatsReader.java b/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/FlowStatsReader.java
new file mode 100644 (file)
index 0000000..f32dd00
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2015 NEC Corporation
+ * All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.vtn.manager.internal.flow.stats;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.opendaylight.vtn.manager.internal.util.concurrent.VTNFuture;
+import org.opendaylight.vtn.manager.internal.util.flow.FlowCache;
+import org.opendaylight.vtn.manager.internal.util.inventory.SalNode;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.VtnFlowEntry;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecord;
+
+/**
+ * {@code FlowStatsReader} reads statistics information about the specified
+ * flow entries from switches.
+ */
+public abstract class FlowStatsReader {
+    /**
+     * A threshold to switch method to read flow statistics.
+     *
+     * <p>
+     *   If the estimated number of flow entries exceeds this value, this class
+     *   will read statistics for all flow entries in switches by one RPC.
+     *   Otherwise this class will read statistics for each flow entry one by
+     *   one.
+     * </p>
+     */
+    private static final int  COUNT_THRESHOLD = 10;
+
+    /**
+     * A flow statistics reader which never reads flow statistics.
+     */
+    private static final NullReader  NULL_READER = new NullReader();
+
+    /**
+     * Return a flow statistics reader.
+     *
+     * @param srs    The flow statistics reader service.
+     * @param count  The estimated number of flow entries present in switches.
+     * @return  A {@link FlowStatsReader} instance.
+     */
+    public static final FlowStatsReader getInstance(
+        StatsReaderService srs, int count) {
+        if (srs == null) {
+            // No need to update flow statistics.
+            return NULL_READER;
+        }
+
+        return (count > COUNT_THRESHOLD)
+            ? new MultiReader(srs)
+            : new SingleReader(srs);
+    }
+
+    /**
+     * Construct a new instance.
+     */
+    private FlowStatsReader() {
+    }
+
+    /**
+     * Return a future associated with the transaction for reading flow
+     * statistics information.
+     *
+     * @param fc  The target flow entry.
+     * @return  A {@link VTNFuture} instance which returns the flow statistics
+     *          about the specified flow entry.
+     */
+    public abstract VTNFuture<FlowStatsRecord> get(FlowCache fc);
+
+    /**
+     * An implementation of {@link FlowStatsReader} which never reads flow
+     * statistics.
+     *
+     * <p>
+     *   This reader class is used when flow statistics does not need to be
+     *   updated.
+     * </p>
+     */
+    private static final class NullReader extends FlowStatsReader {
+        // FlowStatsReader
+
+        /**
+         * {@code null} is always returned because this class never reads
+         * flow statistics.
+         *
+         * @param fc  The target flow entry.
+         * @return  {@code null}.
+         */
+        @Override
+        public VTNFuture<FlowStatsRecord> get(FlowCache fc) {
+            return null;
+        }
+    }
+
+    /**
+     * An implementation of {@link FlowStatsReader} which reads flow statistics
+     * one by one.
+     */
+    private static final class SingleReader extends FlowStatsReader {
+        /**
+         * The flow statistics reader service.
+         */
+        private final StatsReaderService  statsReader;
+
+        /**
+         * Construct a new instance.
+         *
+         * @param srs  The flow statistics reader service.
+         */
+        private SingleReader(StatsReaderService srs) {
+            statsReader = srs;
+        }
+
+        // FlowStatsReader
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public VTNFuture<FlowStatsRecord> get(FlowCache fc) {
+            VtnFlowEntry ingress = fc.getIngressFlow();
+            return (ingress == null) ? null : statsReader.get(ingress);
+        }
+    }
+
+    /**
+     * An implementation of {@link FlowStatsReader} which reads flow statistics
+     * by reading all flow entries in the target switches.
+     */
+    private static final class MultiReader extends FlowStatsReader {
+        /**
+         * The flow statistics reader service.
+         */
+        private final StatsReaderService  statsReader;
+
+        /**
+         * A map which keeps statistics read transaction per switch.
+         */
+        private final ConcurrentMap<SalNode, NodeFlowStatsReader> txMap =
+            new ConcurrentHashMap<>();
+
+        /**
+         * Construct a new instance.
+         *
+         * @param srs  The flow statistics reader service.
+         */
+        private MultiReader(StatsReaderService srs) {
+            statsReader = srs;
+        }
+
+        /**
+         * Return a flow statistics reader which read all flow statistics
+         * present in the given switch.
+         *
+         * @param snode  A {@link SalNode} instance which specifies the switch.
+         * @return  A {@link NodeFlowStatsReader} instance.
+         */
+        private NodeFlowStatsReader getNodeFlowStatsReader(SalNode snode) {
+            NodeFlowStatsReader reader = txMap.get(snode);
+            if (reader == null) {
+                // Create a new flow statistics reader for the given node.
+                reader = new NodeFlowStatsReader(snode);
+                NodeFlowStatsReader old = txMap.putIfAbsent(snode, reader);
+                if (old == null) {
+                    // Start the read transaction.
+                    reader.start(statsReader);
+                } else {
+                    // Lost the race.
+                    reader = old;
+                }
+            }
+
+            return reader;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public VTNFuture<FlowStatsRecord> get(FlowCache fc) {
+            VtnFlowEntry ingress = fc.getIngressFlow();
+            if (ingress == null) {
+                return null;
+            }
+
+            SalNode snode = SalNode.create(ingress.getNode());
+            if (snode == null) {
+                // This should never happen.
+                return null;
+            }
+
+            return getNodeFlowStatsReader(snode).get(ingress);
+        }
+    }
+}
diff --git a/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/NodeFlowStatsReader.java b/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/NodeFlowStatsReader.java
new file mode 100644 (file)
index 0000000..44835f3
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2015 NEC Corporation
+ * All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.vtn.manager.internal.flow.stats;
+
+import java.math.BigInteger;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.opendaylight.vtn.manager.internal.util.concurrent.SettableVTNFuture;
+import org.opendaylight.vtn.manager.internal.util.concurrent.VTNFuture;
+import org.opendaylight.vtn.manager.internal.util.inventory.SalNode;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowAndStatisticsMap;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.statistics.types.rev130925.GenericStatistics;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.VtnFlowEntry;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecord;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecordBuilder;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
+
+/**
+ * {@code NodeFlowStatsReader} reads statistics information about the specified
+ * flow entries in the same switch.
+ *
+ * <p>
+ *   Note that this method reads all flow statistics present in the specified
+ *   switch.
+ * </p>
+ */
+public final class NodeFlowStatsReader implements StatsReaderCallback {
+    /**
+     * Initial transaction state.
+     */
+    private static final int  STATE_INITIAL = 0;
+
+    /**
+     * Transaction state which indicates the transaction is being started.
+     */
+    private static final int  STATE_STARTING = 1;
+
+    /**
+     * Transaction state which indicates the transaction was started
+     * successfully.
+     */
+    private static final int  STATE_STARTED = 2;
+
+    /**
+     * Transaction state which indicates the transaction completed.
+     */
+    private static final int  STATE_COMPLETED = 3;
+
+    /**
+     * Transaction state which indicates the transaction failed to start.
+     */
+    private static final int  STATE_FAILED = 4;
+
+    /**
+     * A map that keeps statistics read transactions and results.
+     */
+    private final ConcurrentMap<BigInteger, SettableVTNFuture<FlowStatsRecord>>  txMap =
+        new ConcurrentHashMap<>();
+
+    /**
+     * The target switch.
+     */
+    private final SalNode  targetNode;
+
+    /**
+     * The statistics read transaction state.
+     */
+    private final AtomicInteger  txState = new AtomicInteger(STATE_INITIAL);
+
+    /**
+     * Construct a new instance.
+     *
+     * @param snode  A {@link SalNode} which specifies the target switch.
+     */
+    public NodeFlowStatsReader(SalNode snode) {
+        targetNode = snode;
+    }
+
+    /**
+     * Return a future associated with the task to read flow statistics
+     * about the given VTN flow entry.
+     *
+     * @param vfent  The target flow entry.
+     * @return  A {@link VTNFuture} instance which returns the flow statistics
+     *          about the specified flow entry.
+     */
+    public VTNFuture<FlowStatsRecord> get(VtnFlowEntry vfent) {
+        assert vfent.getNode().equals(targetNode.getNodeId());
+
+        BigInteger cookie = vfent.getCookie().getValue();
+        SettableVTNFuture<FlowStatsRecord> f = getFuture(cookie);
+        if (txState.get() >= STATE_COMPLETED) {
+            // No more statistics will be received.
+            f.set(null);
+        }
+
+        return f;
+    }
+
+    /**
+     * Start the flow statistics read transaction.
+     *
+     * <p>
+     *   This method must be called only once.
+     *   No flow statistics will be returned if this method is not called.
+     * </p>
+     *
+     * @param srs  The flow statistics reader service.
+     */
+    public void start(StatsReaderService srs) {
+        if (txState.compareAndSet(STATE_INITIAL, STATE_STARTING)) {
+            boolean succeeded = false;
+            try {
+                if (srs.start(targetNode, this)) {
+                    txState.compareAndSet(STATE_STARTING, STATE_STARTED);
+                    succeeded = true;
+                }
+            } finally {
+                if (!succeeded) {
+                    txState.set(STATE_FAILED);
+                    canceled();
+                }
+            }
+        }
+    }
+
+    /**
+     * Return a future which returns the flow statistics information about the
+     * given VTN data flow.
+     *
+     * @param cookie  The flow cookie which specifies the target flow entry.
+     * @return  A {@link SettableVTNFuture} instance.
+     */
+    private SettableVTNFuture<FlowStatsRecord> getFuture(BigInteger cookie) {
+        SettableVTNFuture<FlowStatsRecord> f = txMap.get(cookie);
+        if (f == null) {
+            // Associate a new feature with the given VTN data flow.
+            f = new SettableVTNFuture<>();
+            SettableVTNFuture<FlowStatsRecord> old =
+                txMap.putIfAbsent(cookie, f);
+            if (old != null) {
+                // Lost the race.
+                f = old;
+            }
+        }
+
+        return f;
+    }
+
+    /**
+     * Invoked when the statistics read transaction has completed.
+     */
+    private void completed() {
+        txState.set(STATE_COMPLETED);
+        canceled();
+    }
+
+    /**
+     * Make all requests for statistics fail.
+     */
+    private void canceled() {
+        for (SettableVTNFuture<FlowStatsRecord> f: txMap.values()) {
+            // This does nothing if this future has a result.
+            f.set(null);
+        }
+    }
+
+    // StatsReaderCallback
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void flowStatsReceived(NodeId node, FlowAndStatisticsMap fstats) {
+        assert node.equals(targetNode.getNodeId());
+
+        FlowCookie cookie = fstats.getCookie();
+        if (cookie != null) {
+            Long time = System.currentTimeMillis();
+            GenericStatistics gen = fstats;
+            FlowStatsRecord fsr = new FlowStatsRecordBuilder(gen).
+                setTime(time).build();
+            getFuture(cookie.getValue()).set(fsr);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void transactionCompleted() {
+        completed();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void transactionCanceled() {
+        completed();
+    }
+}
diff --git a/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/StatsReaderCallback.java b/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/StatsReaderCallback.java
new file mode 100644 (file)
index 0000000..6ca531b
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015 NEC Corporation
+ * All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.vtn.manager.internal.flow.stats;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowAndStatisticsMap;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+
+/**
+ * {@code StatsReaderCallback} is a callback for accepting flow statistics
+ * information sent by a switch.
+ *
+ * <p>
+ *   This callback is always invoked on a global single thread.
+ * </p>
+ */
+public interface StatsReaderCallback {
+    /**
+     * Invoked when a flow statistics information has been received.
+     *
+     * @param node    A {@link NodeId} instance which specifies the switch
+     *                which contains the flow entry.
+     * @param fstats  A {@link FlowAndStatisticsMap} instance which contains
+     *                the flow statistics send by the switch.
+     */
+    void flowStatsReceived(NodeId node, FlowAndStatisticsMap fstats);
+
+    /**
+     * Invoked when the transaction for reading flow statistics has been
+     * completed successfully.
+     */
+    void transactionCompleted();
+
+    /**
+     * Invoked when the transaction for reading flow statistics has been
+     * canceled.
+     */
+    void transactionCanceled();
+}
diff --git a/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/StatsReaderService.java b/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/flow/stats/StatsReaderService.java
new file mode 100644 (file)
index 0000000..93e478b
--- /dev/null
@@ -0,0 +1,714 @@
+/*
+ * Copyright (c) 2015 NEC Corporation
+ * All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.vtn.manager.internal.flow.stats;
+
+import static org.opendaylight.vtn.manager.internal.util.MiscUtils.VERBOSE_LOG;
+import static org.opendaylight.vtn.manager.internal.util.MiscUtils.checkNotNull;
+
+import java.math.BigInteger;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.opendaylight.vtn.manager.VTNException;
+
+import org.opendaylight.vtn.manager.internal.VTNManagerProvider;
+import org.opendaylight.vtn.manager.internal.util.SalNotificationListener;
+import org.opendaylight.vtn.manager.internal.util.concurrent.SettableVTNFuture;
+import org.opendaylight.vtn.manager.internal.util.concurrent.VTNFuture;
+import org.opendaylight.vtn.manager.internal.util.concurrent.VTNThreadPool;
+import org.opendaylight.vtn.manager.internal.util.flow.FlowEntryDesc;
+import org.opendaylight.vtn.manager.internal.util.flow.FlowStatsUtils;
+import org.opendaylight.vtn.manager.internal.util.flow.GetFlowStatsRpc;
+import org.opendaylight.vtn.manager.internal.util.inventory.SalNode;
+
+import org.opendaylight.controller.sal.binding.api.NotificationService;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.VtnFlowEntry;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecord;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecordBuilder;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.AggregateFlowStatisticsUpdate;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowAndStatisticsMap;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowsStatisticsUpdate;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetFlowStatisticsFromFlowTableInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.OpendaylightFlowStatisticsListener;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.OpendaylightFlowStatisticsService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.flow.and.statistics.map.list.FlowAndStatisticsMapList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.TransactionId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.statistics.types.rev130925.GenericStatistics;
+
+/**
+ * {@code StatsReaderService} provides interfaces to fetch flow statistics
+ * information from OpenFlow switch.
+ */
+public final class StatsReaderService extends SalNotificationListener
+    implements OpendaylightFlowStatisticsListener {
+    /**
+     * Logger instance.
+     */
+    private static final Logger  LOG =
+        LoggerFactory.getLogger(StatsReaderService.class);
+
+    /**
+     * The capacity of the task queue.
+     */
+    private static final int  TASK_CAPACITY = 1000000;
+
+    /**
+     * The maximum number of milliseconds to wait for completion of RPC.
+     */
+    private static final long  RPC_TIMEOUT = 5000L;
+
+    /**
+     * The timeout for transaction in milliseconds.
+     */
+    private static final long  TX_TIMEOUT = 10000L;
+
+    /**
+     * The number of milliseconds to wait for completion of the task thread.
+     */
+    private static final long  SHUTDOWN_TIMEOUT = 10000L;
+
+    /**
+     * MD-SAL flow statistics service.
+     */
+    private OpendaylightFlowStatisticsService  statsService;
+
+    /**
+     * A timer thread.
+     */
+    private final Timer  timer;
+
+    /**
+     * Active transactions.
+     */
+    private final ConcurrentMap<String, ReaderContext>  txMap =
+        new ConcurrentHashMap<>();
+
+    /**
+     * A thread to execute transaction tasks.
+     */
+    private final VTNThreadPool  taskThread;
+
+    /**
+     * {@code ReaderContext} describes a transaction associated with a request
+     * for flow statistics.
+     */
+    private final class ReaderContext extends TimerTask {
+        /**
+         * The identifier associated with this transaction.
+         */
+        private final String  transactionKey;
+
+        /**
+         * The callback to be invoked when flow statistics are received.
+         */
+        private final StatsReaderCallback  callback;
+
+        /**
+         * Construct a new instance.
+         *
+         * @param xkey  The key string which identifies the transaction.
+         * @param cb    The callback to be invoked when flow statistics are
+         *              received.
+         */
+        private ReaderContext(String xkey, StatsReaderCallback cb) {
+            transactionKey = xkey;
+            callback = cb;
+        }
+
+        /**
+         * Return the transaction key.
+         *
+         * @return  The transaction key.
+         */
+        private String getTransactionKey() {
+            return transactionKey;
+        }
+
+        /**
+         * Return the callback.
+         *
+         * @return  A {@link StatsReaderCallback} instance.
+         */
+        private StatsReaderCallback getCallback() {
+            return callback;
+        }
+
+        // Runnable
+
+        /**
+         * Post a task which expires this transaction.
+         */
+        @Override
+        public void run() {
+            if (!taskThread.executeTask(new ExpireTask(this))) {
+                expire(this);
+            }
+        }
+    }
+
+    /**
+     * {@code ExpireTask} expires the transaction.
+     */
+    private final class ExpireTask implements Runnable {
+        /**
+         * The target reader context.
+         */
+        private final ReaderContext  context;
+
+        /**
+         * Construct a new instance.
+         *
+         * @param ctx  The target reader context.
+         */
+        private ExpireTask(ReaderContext ctx) {
+            context = ctx;
+        }
+
+        // Runnable
+
+        /**
+         * Expire the target transaction.
+         */
+        @Override
+        public void run() {
+            expire(context);
+        }
+    }
+
+    /**
+     * {@code RpcTask} is a base class for tasks which issue an RPC request.
+     */
+    private abstract class RpcTask implements Runnable {
+        /**
+         * Return a MD-SAL node identifier which indicates the target switch.
+         *
+         * @return  A MD-SAL node identifier.
+         */
+        protected abstract String getTargetNodeId();
+
+        /**
+         * Return the callback associated with the transaction.
+         *
+         * @return  A {@link StatsReaderCallback} instance.
+         */
+        protected abstract StatsReaderCallback getCallback();
+
+        /**
+         * Return an RPC input for getting flow statistics.
+         *
+         * @return  A {@link GetFlowStatisticsFromFlowTableInput} instance.
+         */
+        protected abstract GetFlowStatisticsFromFlowTableInput getInput();
+
+        /**
+         * Return a brief description about the request.
+         *
+         * <p>
+         *   This method is used only for debugging.
+         * </p>
+         *
+         * @return  A brief description about the request.
+         */
+        protected abstract String getDescription();
+
+        // Runnable
+
+        /**
+         * Issue an RPC request for getting flow statistics.
+         */
+        @Override
+        public final void run() {
+            String nid = getTargetNodeId();
+            GetFlowStatisticsFromFlowTableInput input = getInput();
+            GetFlowStatsRpc rpc = new GetFlowStatsRpc(statsService, input);
+            StatsReaderCallback cb = getCallback();
+            String xkey = startTransaction(nid, rpc, cb);
+            if (xkey == null) {
+                cb.transactionCanceled();
+            } else if (LOG.isDebugEnabled()) {
+                LOG.debug("Read transaction has started: xid={}, input={}",
+                          xkey, getDescription());
+            }
+        }
+    }
+
+    /**
+     * {@code SingleFlowStatsTask} issues an RPC request which requests
+     * statistics information about the given flow entry.
+     */
+    private final class SingleFlowStatsTask extends RpcTask
+        implements StatsReaderCallback {
+        /**
+         * The target flow entry.
+         */
+        private final VtnFlowEntry  targetFlow;
+
+        /**
+         * The node identifier which specifies the target node.
+         */
+        private final String  targetNodeId;
+
+        /**
+         * The flow cookie configured in the target VTN flow entry.
+         */
+        private final FlowCookie  targetCookie;
+
+        /**
+         * A future associated with the transaction for getting the statistics.
+         */
+        private final SettableVTNFuture<FlowStatsRecord>  future =
+            new SettableVTNFuture<>();
+
+        /**
+         * Construct a new instance.
+         *
+         * @param vfent  The target VTN flow entry.
+         */
+        private SingleFlowStatsTask(VtnFlowEntry vfent) {
+            NodeId nid = checkNotNull(vfent.getNode(), LOG,
+                                      "No node in VTN flow entry.");
+            targetFlow = vfent;
+            targetNodeId = nid.getValue();
+            targetCookie = vfent.getCookie();
+        }
+
+        /**
+         * Return the future associated with the read transaction.
+         *
+         * @return  A {@link VTNFuture} instance.
+         */
+        private SettableVTNFuture<FlowStatsRecord> getFuture() {
+            return future;
+        }
+
+        // RpcTask
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected String getTargetNodeId() {
+            return targetNodeId;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected StatsReaderCallback getCallback() {
+            return this;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected GetFlowStatisticsFromFlowTableInput getInput() {
+            return FlowStatsUtils.createGetFlowStatsInput(targetFlow);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected String getDescription() {
+            return new StringBuilder("SingleFlow[").
+                append(new FlowEntryDesc(targetFlow, false)).append(']').
+                toString();
+        }
+
+        // StatsReaderCallback
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void flowStatsReceived(NodeId node,
+                                      FlowAndStatisticsMap fstats) {
+            if (targetNodeId.equals(node.getValue()) &&
+                targetCookie.equals(fstats.getCookie())) {
+                Long time = System.currentTimeMillis();
+                GenericStatistics gen = fstats;
+                FlowStatsRecord fsr = new FlowStatsRecordBuilder(gen).
+                    setTime(time).build();
+                future.set(fsr);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void transactionCompleted() {
+            // Set null as the result of the future.
+            if (future.set(null) && LOG.isDebugEnabled()) {
+                LOG.debug("Flow statistics not found: {}", getDescription());
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void transactionCanceled() {
+            future.cancel(false);
+        }
+    }
+
+    /**
+     * {@code AllFlowStatsTask} issues an RPC request which requests statistics
+     * information about all the flow entries present in the given switch.
+     */
+    private final class AllFlowStatsTask extends RpcTask {
+        /**
+         * The target switch.
+         */
+        private final SalNode  targetNode;
+
+        /**
+         * The flow stats reader callback.
+         */
+        private final StatsReaderCallback  callback;
+
+        /**
+         * Construct a new instance.
+         *
+         * @param snode  The target switch.
+         * @param cb     The callback to be invoked when flow statistics are
+         *               received.
+         */
+        private AllFlowStatsTask(SalNode snode, StatsReaderCallback cb) {
+            targetNode =
+                checkNotNull(snode, LOG, "Target node cannot be null.");
+            callback = cb;
+        }
+
+        // RpcTask
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected String getTargetNodeId() {
+            return targetNode.toString();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected StatsReaderCallback getCallback() {
+            return callback;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected GetFlowStatisticsFromFlowTableInput getInput() {
+            return FlowStatsUtils.createGetFlowStatsInput(targetNode);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected String getDescription() {
+            return new StringBuilder("AllFlows[").
+                append(targetNode).append(']').toString();
+        }
+    }
+
+    /**
+     * {@code NotificationTask} notifies the flow-statistics-update
+     * notification to callback associated with the notification.
+     */
+    private final class NotificationTask implements Runnable {
+        /**
+         * The key string which specifies the tranaction.
+         */
+        private final String  transactionKey;
+
+        /**
+         * A flow-statistics-update notification.
+         */
+        private final FlowsStatisticsUpdate  notification;
+
+        /**
+         * Construct a new instance.
+         *
+         * @param xkey    The key string which identifies the transaction.
+         * @param update  A flow-statistics-update notification.
+         */
+        private NotificationTask(String xkey, FlowsStatisticsUpdate update) {
+            transactionKey = xkey;
+            notification = update;
+        }
+
+        // Runnable
+
+        /**
+         * Notifiy the flow statistics to the callback associated with the
+         * transaction.
+         */
+        @Override
+        public void run() {
+            invoke(transactionKey, notification);
+        }
+    }
+
+    /**
+     * Construct a new instance.
+     *
+     * @param provider  A VTN Manager provider service.
+     * @param nsv       A {@link NotificationService} service instance.
+     */
+    public StatsReaderService(VTNManagerProvider provider,
+                              NotificationService nsv) {
+        timer = provider.getTimer();
+        taskThread = new VTNThreadPool("StatsReader Task Thread");
+        addCloseable(taskThread);
+
+        try {
+            // Get MD-SAL RPC services.
+            statsService = provider.
+                getRpcService(OpendaylightFlowStatisticsService.class);
+
+            // Register MD-SAL notification listener.
+            registerListener(nsv);
+        } catch (Exception e) {
+            String msg = "Failed to initialize VTN flow statistics reader.";
+            LOG.error(msg, e);
+            close();
+            throw new IllegalStateException(msg, e);
+        }
+    }
+
+    /**
+     * Get the statistics information about the given VTN flow entry.
+     *
+     * @param vfent  The target VTN flow entry.
+     * @return  A {@link VTNFuture} instance associated with the stats read
+     *          transaction.
+     */
+    public VTNFuture<FlowStatsRecord> get(VtnFlowEntry vfent) {
+        SingleFlowStatsTask task = new SingleFlowStatsTask(vfent);
+        SettableVTNFuture<FlowStatsRecord> f = task.getFuture();
+        if (!taskThread.executeTask(task)) {
+            f.set(null);
+        }
+
+        return f;
+    }
+
+    /**
+     * Start the flow stats reader transaction for getting statistics
+     * information about all flow entries present in the given switch.
+     *
+     * @param snode  The target switch.
+     * @param cb     The callback to be invoked when flow statistics are
+     *               received.
+     * @return  {@code true} on success. {@code false} on failure.
+     */
+    public boolean start(SalNode snode, StatsReaderCallback cb) {
+        return taskThread.executeTask(new AllFlowStatsTask(snode, cb));
+    }
+
+    /**
+     * Shut down the flow statistics reader service.
+     */
+    public void shutdown() {
+        taskThread.shutdown();
+    }
+
+    /**
+     * Wait for completion of the given RPC, and return the transaction ID
+     * return by the RPC.
+     *
+     * @param nid  A MD-SAL node identifier which specifies the target switch.
+     * @param rpc  A {@link GetFlowStatsRpc} instance.
+     * @param cb   A callback associated with the given RPC.
+     * @return  The key string associated with the read transaction on success.
+     *          {@code null} on failure.
+     */
+    private String startTransaction(String nid, GetFlowStatsRpc rpc,
+                                    StatsReaderCallback cb) {
+        try {
+            BigInteger xid = rpc.getTransactionId(
+                RPC_TIMEOUT, TimeUnit.MILLISECONDS, LOG);
+            String xkey = FlowStatsUtils.createTransactionKey(nid, xid);
+            ReaderContext ctx = new ReaderContext(xkey, cb);
+            if (txMap.putIfAbsent(xkey, ctx) == null) {
+                // Register expiration timer.
+                timer.schedule(ctx, TX_TIMEOUT);
+                return xkey;
+            }
+            ctx.cancel();
+            LOG.error("Duplicated XID: {}", xkey);
+        } catch (VTNException | RuntimeException e) {
+            LOG.error("Failed to issue flow stats RPC: " + rpc.getName(), e);
+        }
+
+        return null;
+    }
+
+    /**
+     * Invoke the callback associated with the given transaction.
+     *
+     * @param xkey    The key string which identifies the transaction.
+     * @param update  A flow-statistics-update notification.
+     */
+    private void invoke(String xkey, FlowsStatisticsUpdate update) {
+        ReaderContext ctx = txMap.get(xkey);
+        if (ctx == null) {
+            VERBOSE_LOG.trace("Ignore unwanted notification: xid={}", xkey);
+            return;
+        }
+
+        StatsReaderCallback cb = ctx.getCallback();
+        List<FlowAndStatisticsMapList> mapList =
+            update.getFlowAndStatisticsMapList();
+        if (mapList != null) {
+            NodeId node = update.getId();
+            for (FlowAndStatisticsMapList map: mapList) {
+                String err = FlowStatsUtils.check(map);
+                if (err == null) {
+                    cb.flowStatsReceived(node, map);
+                } else {
+                    LOG.warn("Ignore broken flow statistics: {}: xid={}, " +
+                             "flow={}", err, xkey, map);
+                }
+            }
+        }
+
+        if (!Boolean.TRUE.equals(update.isMoreReplies()) &&
+            txMap.remove(xkey, ctx)) {
+            // All statistics has been received.
+            ctx.cancel();
+            LOG.debug("Read transaction has completed: xid={}", xkey);
+            cb.transactionCompleted();
+        }
+    }
+
+    /**
+     * Expire the given transaction.
+     *
+     * @param ctx  The target reader context.
+     */
+    private void expire(ReaderContext ctx) {
+        String xkey = ctx.getTransactionKey();
+        if (txMap.remove(xkey, ctx)) {
+            LOG.error("Transaction has expired: xid={}", xkey);
+            ctx.getCallback().transactionCanceled();
+        }
+    }
+
+    /**
+     * Record a broken notification as a warning message.
+     * ignored.
+     *
+     * @param notification  A {@link FlowsStatisticsUpdate} instance.
+     * @param msg           A warning message.
+     */
+    private void broken(FlowsStatisticsUpdate notification, String msg) {
+        LOG.warn("Ignore broken flow-statistics-update notification: {}: {}",
+                 msg, notification);
+    }
+
+    // AutoCloseable
+
+    /**
+     * Close the flow statistics reader service.
+     */
+    @Override
+    public void close() {
+        shutdown();
+
+        // Cancel all transactions.
+        while (!txMap.isEmpty()) {
+            Set<String> xkeys = new HashSet<>(txMap.keySet());
+            for (String xkey: xkeys) {
+                ReaderContext ctx = txMap.remove(xkey);
+                if (ctx != null) {
+                    ctx.cancel();
+                    ctx.getCallback().transactionCanceled();
+                }
+            }
+        }
+
+        super.close();
+    }
+
+    // CloseableContainer
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected Logger getLogger() {
+        return LOG;
+    }
+
+    // OpendaylightFlowStatisticsListener
+
+    /**
+     * Invoked when an aggregated flow statistics for a flow table has been
+     * send by a switch.
+     *
+     * @param notification  An {@link AggregateFlowStatisticsUpdate} instance.
+     */
+    @Override
+    public void onAggregateFlowStatisticsUpdate(
+        AggregateFlowStatisticsUpdate notification) {
+        // Nothing to do.
+    }
+
+    /**
+     * Invoked when a flow statistics has been send by a switch.
+     *
+     * @param notification  A {@link FlowsStatisticsUpdate} instance.
+     */
+    @Override
+    public void onFlowsStatisticsUpdate(FlowsStatisticsUpdate notification) {
+        if (notification == null) {
+            LOG.warn("Null flow-statistics-update notification.");
+            return;
+        }
+
+        NodeId node = notification.getId();
+        if (node == null) {
+            broken(notification, "No node-id");
+            return;
+        }
+
+        TransactionId txId = notification.getTransactionId();
+        if (txId == null) {
+            broken(notification, "No XID");
+            return;
+        }
+
+        String xkey = FlowStatsUtils.createTransactionKey(
+            node.getValue(), txId.getValue());
+        taskThread.executeTask(new NotificationTask(xkey, notification));
+    }
+}
index ec6a69b9d4ce3524998297f165def96519f8e25f..c5dcae9199f5373a4ca4dbbf5d6af5835ee67e6f 100644 (file)
@@ -42,8 +42,6 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.ten
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.tenant.flow.info.VtnDataFlowKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.FlowStatsHistory;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.VtnFlowEntry;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecord;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecordBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.flows.VtnFlowTable;
 
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
@@ -152,20 +150,14 @@ public final class StatsTimerTask extends TimerTask implements AutoCloseable {
             }
 
             // Update the hisotry of flow statistics if needed.
-            FlowStatsRecord fsr = new FlowStatsRecordBuilder(fstats).
-                setTime(systemTime).setPeriodic(true).build();
-            FlowStatsHistory history =
-                FlowStatsUtils.update(vdf.getFlowStatsHistory(), fsr);
-            if (history == null) {
-                traceLog("Skip flow entry: %s: Too rapid.", id);
-            } else {
-                traceLog("Flow statistics has been updated: %s, %s",
-                         id, fid.getValue());
-                InstanceIdentifier<FlowStatsHistory> path =
-                    FlowStatsUtils.getIdentifier(tname, key);
-                LogicalDatastoreType oper = LogicalDatastoreType.OPERATIONAL;
-                tx.put(oper, path, history, false);
-            }
+            FlowStatsHistory history = FlowStatsUtils.addPeriodic(
+                vdf.getFlowStatsHistory(), systemTime, fstats);
+            InstanceIdentifier<FlowStatsHistory> path =
+                FlowStatsUtils.getIdentifier(tname, key);
+            LogicalDatastoreType oper = LogicalDatastoreType.OPERATIONAL;
+            tx.put(oper, path, history, false);
+            traceLog("Flow statistics has been updated: %s, %s",
+                     id, fid.getValue());
         }
 
         /**
index 06988f850dc64e436ae3d176a5da1dcd7b2ee097..3f29b83a5ff99426f2066db314d54a753fcd3407 100644 (file)
@@ -9,6 +9,7 @@
 
 package org.opendaylight.vtn.manager.internal.inventory;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -77,6 +78,19 @@ abstract class InventoryUpdateTask<T extends DataObject, L>
         return !updated.isEmpty();
     }
 
+    /**
+     * Return a map which keeps updated inventories.
+     *
+     * <p>
+     *   This method is used only for test.
+     * </p>
+     *
+     * @return  A map which keeps updated inventories.
+     */
+    final Map<L, InstanceIdentifier<T>> getUpdatedMap() {
+        return Collections.unmodifiableMap(updated);
+    }
+
     /**
      * Return a {@link Logger} instance.
      *
index 6cc15b784208848c66ffae6c252b5a31d8bea2cf..3f7b1e61587ff2646dba45109a1017e1ef6f5f52 100644 (file)
@@ -11,6 +11,8 @@ package org.opendaylight.vtn.manager.internal.inventory;
 
 import org.slf4j.Logger;
 
+import com.google.common.base.Optional;
+
 import org.opendaylight.vtn.manager.VTNException;
 
 import org.opendaylight.vtn.manager.internal.TxContext;
@@ -24,9 +26,10 @@ import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 
-import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.inventory.rev150209.vtn.nodes.VtnNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.inventory.rev150209.vtn.nodes.VtnNodeBuilder;
 
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
 
 /**
  * A MD-SAL datastore transaction task that updates VTN node information.
@@ -60,11 +63,21 @@ final class NodeUpdateTask
     protected void add(TxContext ctx, ReadWriteTransaction tx, SalNode snode,
                        InstanceIdentifier<FlowCapableNode> path,
                        FlowCapableNode fcn) throws VTNException {
+        // Read MD-SAL node.
+        InstanceIdentifier<Node> npath = snode.getNodeIdentifier();
+        LogicalDatastoreType oper = LogicalDatastoreType.OPERATIONAL;
+        Optional<Node> opt = DataStoreUtils.read(tx, oper, npath);
+        VtnNodeBuilder builder;
+        if (opt.isPresent()) {
+            builder = InventoryUtils.toVtnNodeBuilder(opt.get());
+        } else {
+            // The given MD-SAL node is no longer present.
+            // VTN node will be removed soon.
+            builder = InventoryUtils.toVtnNodeBuilder(snode.getNodeId());
+        }
+
         // Create a VTN node.
-        VtnNode vnode = InventoryUtils.toVtnNodeBuilder(snode.getNodeId()).
-            build();
-        tx.merge(LogicalDatastoreType.OPERATIONAL,
-                 snode.getVtnNodeIdentifier(), vnode, true);
+        tx.merge(oper, snode.getVtnNodeIdentifier(), builder.build(), true);
     }
 
     /**
index e5b513557597dce1de2f78fe4757815d4a611f4d..1a13ef8ad3386c6ab883a7ebeab8d295dc129af5 100644 (file)
@@ -86,8 +86,8 @@ public final class VTNInventoryManager
      */
     static {
         PATH_COMPARATOR = new IdentifierTargetComparator().
-            setOrder(VtnPort.class, 1).
-            setOrder(VtnNode.class, 2);
+            setOrder(VtnNode.class, 1).
+            setOrder(VtnPort.class, 2);
     }
 
     /**
@@ -282,9 +282,9 @@ public final class VTNInventoryManager
      */
     @Override
     protected boolean getOrder(VtnUpdateType type) {
-        // Creation events should be processed from outer to inner.
-        // Other events should be processed from inner to outer.
-        return (type != VtnUpdateType.CREATED);
+        // Removal events should be processed from inner to outer.
+        // Other events should be processed from outer to inner.
+        return (type != VtnUpdateType.REMOVED);
     }
 
     /**
index 1417b9275a333e2a36ada3ac657cce726c117b0b..23ba8fa66dab5616b1a7810edecd9d389f184e88 100644 (file)
@@ -119,6 +119,15 @@ public class VTNThreadPool extends AbstractExecutorService
      */
     private int  poolState = STATE_RUNNING;
 
+    /**
+     * Construct a new thread pool which has a single worker thread.
+     *
+     * @param prefix  A prefix for the name of worker threads.
+     */
+    public VTNThreadPool(String prefix) {
+        this(prefix, 1, 0);
+    }
+
     /**
      * Construct a new thread pool.
      *
@@ -381,7 +390,7 @@ public class VTNThreadPool extends AbstractExecutorService
          * Main routine of worker thread.
          */
         @Override
-        public void run() {
+        public final void run() {
             LOG.trace("Start");
 
             for (Runnable r = waitFor(); r != null; r = waitFor()) {
@@ -399,13 +408,13 @@ public class VTNThreadPool extends AbstractExecutorService
     /**
      * {@code MainThread} class implements the main thread of the pool.
      */
-    protected class MainThread extends WorkerThread {
+    private final class MainThread extends WorkerThread {
         /**
          * Construct a main thread.
          *
          * @param name  The name of this thread.
          */
-        protected MainThread(String name) {
+        private MainThread(String name) {
             super(name);
         }
 
index 5573722333ec46b051695e8d1f74d49020c9f8c8..0a2bba244aa71be7e4a36030800af065cde4ea96 100644 (file)
@@ -39,12 +39,31 @@ public final class FlowEntryDesc {
     private final VtnFlowEntry  flowEntry;
 
     /**
-     * Construct a new instance.
+     * Determine whether flow instructions should be embedded into a log
+     * record or not.
+     */
+    private final boolean  needInstruction;
+
+    /**
+     * Construct a new instance which describes a log record including
+     * flow instructions.
      *
      * @param vfent  A {@link VtnFlowEntry} instance.
      */
     public FlowEntryDesc(VtnFlowEntry vfent) {
+        this(vfent, true);
+    }
+
+    /**
+     * Construct a new instance which describes a log record.
+     *
+     * @param vfent  A {@link VtnFlowEntry} instance.
+     * @param inst   Flow instructions are embedded into a log record
+     *               if {@code true}.
+     */
+    public FlowEntryDesc(VtnFlowEntry vfent, boolean inst) {
         flowEntry = vfent;
+        needInstruction = inst;
     }
 
     // Object
@@ -83,18 +102,20 @@ public final class FlowEntryDesc {
             append(',').append(flowEntry.getHardTimeout()).
             append("), node=").append(flowEntry.getNode().getValue()).
             append(", ingress=").append(ingress).
-            append(", cond={").append(cond).
-            append("}, actions={");
+            append(", cond={").append(cond);
 
-        FlowActionConverter converter = FlowActionConverter.getInstance();
-        OrderedComparator comp = new OrderedComparator();
-        List<Action> actions = FlowActionUtils.
-            getActions(flowEntry.getInstructions(), comp);
-        String sep = "";
-        for (Action action: actions) {
-            builder.append(sep).
-                append(converter.getDescription(action.getAction()));
-            sep = ", ";
+        if (needInstruction) {
+            builder.append("}, actions={");
+            FlowActionConverter converter = FlowActionConverter.getInstance();
+            OrderedComparator comp = new OrderedComparator();
+            List<Action> actions = FlowActionUtils.
+                getActions(flowEntry.getInstructions(), comp);
+            String sep = "";
+            for (Action action: actions) {
+                builder.append(sep).
+                    append(converter.getDescription(action.getAction()));
+                sep = ", ";
+            }
         }
 
         return builder.append('}').toString();
index 60a59f84e61376cbd19e7b57319ddc22d328aa6c..1e9c1f1612818cb89db2e6083cb77ff610e2a768 100644 (file)
@@ -9,14 +9,22 @@
 
 package org.opendaylight.vtn.manager.internal.util.flow;
 
+import static org.opendaylight.vtn.manager.internal.util.flow.FlowUtils.COOKIE_MASK_ALL;
+import static org.opendaylight.vtn.manager.internal.util.flow.FlowUtils.COOKIE_MASK_VTN;
+import static org.opendaylight.vtn.manager.internal.util.flow.FlowUtils.COOKIE_VTN;
+import static org.opendaylight.vtn.manager.internal.util.flow.FlowUtils.TABLE_ID;
+
+import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.NavigableMap;
 import java.util.TreeMap;
 
 import org.opendaylight.vtn.manager.VTNException;
 
 import org.opendaylight.vtn.manager.internal.util.DataStoreUtils;
+import org.opendaylight.vtn.manager.internal.util.inventory.SalNode;
 
 import org.opendaylight.controller.md.sal.binding.api.ReadTransaction;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
@@ -26,7 +34,9 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.tenant.flow.info.VtnDataFlowKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.FlowStatsHistory;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.FlowStatsHistoryBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.VtnFlowEntry;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecord;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecordBuilder;
 
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
@@ -35,6 +45,8 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.ta
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowStatisticsData;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetFlowStatisticsFromFlowTableInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetFlowStatisticsFromFlowTableInputBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.flow.statistics.FlowStatistics;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
@@ -49,7 +61,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.model.statistics.types.rev1
  */
 public final class FlowStatsUtils {
     /**
-     * The lifetime, in milliseconds, of statistics history record.
+     * The lifetime of statistics history record in milliseconds.
      */
     private static final long  HISTORY_LIFETIME = 60000L;
 
@@ -63,7 +75,6 @@ public final class FlowStatsUtils {
      */
     private FlowStatsUtils() {}
 
-
     /**
      * Create the instance identifier for the statistics history of the
      * given VTN data flow.
@@ -92,13 +103,55 @@ public final class FlowStatsUtils {
         return InstanceIdentifier.builder(Nodes.class).
             child(Node.class, new NodeKey(node)).
             augmentation(FlowCapableNode.class).
-            child(Table.class, new TableKey(FlowUtils.TABLE_ID)).
+            child(Table.class, new TableKey(TABLE_ID)).
             child(Flow.class, new FlowKey(flowId)).
             augmentation(FlowStatisticsData.class).
             child(FlowStatistics.class).
             build();
     }
 
+    /**
+     * Construct an RPC input which requests flow statistics for the given
+     * VTN flow entry.
+     *
+     * @param vfent  The target VTN flow entry.
+     * @return  A {@link GetFlowStatisticsFromFlowTableInput} instance.
+     */
+    public static GetFlowStatisticsFromFlowTableInput createGetFlowStatsInput(
+        VtnFlowEntry vfent) {
+        GetFlowStatisticsFromFlowTableInputBuilder builder =
+            new GetFlowStatisticsFromFlowTableInputBuilder(vfent);
+
+        SalNode snode = SalNode.create(vfent.getNode());
+        return builder.setNode(snode.getNodeRef()).
+            setCookieMask(COOKIE_MASK_ALL).
+            build();
+    }
+
+    /**
+     * Construct an RPC input which requests statistics information about all
+     * VTN flow entries present in the given switch.
+     *
+     * <p>
+     *   Note that this method uses flow cookie to select VTN flow entries.
+     *   So OpenFlow 1.0 switch will return all the flow entries when it
+     *   receives an input returned by this method.
+     * </p>
+     *
+     * @param snode  A {@link SalNode} instance which specifies the target
+     *               switch.
+     * @return  A {@link GetFlowStatisticsFromFlowTableInput} instance.
+     */
+    public static GetFlowStatisticsFromFlowTableInput createGetFlowStatsInput(
+        SalNode snode) {
+        return new GetFlowStatisticsFromFlowTableInputBuilder().
+            setNode(snode.getNodeRef()).
+            setCookie(COOKIE_VTN).
+            setCookieMask(COOKIE_MASK_VTN).
+            setTableId(TABLE_ID).
+            build();
+    }
+
     /**
      * Read statistics information for the given MD-SAL flow.
      *
@@ -151,15 +204,19 @@ public final class FlowStatsUtils {
     }
 
     /**
-     * Update the flow statistics history.
+     * Put all the flow statistics records in the given statistics history
+     * into a new navigable map.
      *
-     * @param history    History of flow statistics.
-     * @param newRecord  A statistics record to be added.
-     * @return  A {@link FlowStatsHistory} instance if the history has to be
-     *          updated. Otherwise {@code null}.
+     * <p>
+     *   All statistics records are associated with its creation time in a
+     *   returned map.
+     * </p>
+     *
+     * @param history  History of flow statistics.
+     * @return  A {@link NavigableMap} instance.
      */
-    public static FlowStatsHistory update(FlowStatsHistory history,
-                                          FlowStatsRecord newRecord) {
+    public static NavigableMap<Long, FlowStatsRecord> toNavigableMap(
+        FlowStatsHistory history) {
         TreeMap<Long, FlowStatsRecord> map = new TreeMap<>();
         if (history != null) {
             List<FlowStatsRecord> list = history.getFlowStatsRecord();
@@ -170,36 +227,109 @@ public final class FlowStatsUtils {
             }
         }
 
+        return map;
+    }
+
+    /**
+     * Add a periodic flow statistics record to the statistics history.
+     *
+     * @param history  History of flow statistics.
+     * @param sysTime  The system time when the statistics is derived.
+     * @param gstats   A statistics record to be added.
+     * @return  A {@link FlowStatsHistory} instance.
+     */
+    public static FlowStatsHistory addPeriodic(
+        FlowStatsHistory history, Long sysTime, GenericStatistics gstats) {
         // Expire old records.
-        Long newTime = newRecord.getTime();
-        long expire = newTime.longValue() - HISTORY_LIFETIME;
-        for (Long time = map.firstKey(); time != null; time = map.firstKey()) {
-            if (expire > time.longValue()) {
-                map.remove(time);
-            } else {
-                break;
+        Long expire = Long.valueOf(sysTime.longValue() - HISTORY_LIFETIME);
+        NavigableMap<Long, FlowStatsRecord> map =
+            toNavigableMap(history).tailMap(expire, true);
+
+        // Determine the latest record in the given history.
+        GenericStatistics newStats = gstats;
+        Map.Entry<Long, FlowStatsRecord> latest = map.lastEntry();
+        if (latest != null) {
+            FlowStatsRecord lfsr = latest.getValue();
+            if (!Boolean.TRUE.equals(lfsr.isPeriodic())) {
+                // Ensure that the given statistics is newer than the latest
+                // history record.
+                Duration nd = newStats.getDuration();
+                Duration ld = lfsr.getDuration();
+                if (FlowUtils.compare(nd, ld) < 0) {
+                    // The latest record is newer than the given statistics.
+                    // Use the latest record as a new periodic history record.
+                    newStats = lfsr;
+                    map.remove(latest.getKey());
+                }
             }
         }
 
-        // Determine the latest record.
+        // Return a new statistics history.
+        FlowStatsRecord newRecord = new FlowStatsRecordBuilder(newStats).
+            setTime(sysTime).setPeriodic(true).build();
+        map.put(sysTime, newRecord);
+        List<FlowStatsRecord> list = new ArrayList<>(map.values());
+        return new FlowStatsHistoryBuilder().
+            setFlowStatsRecord(list).build();
+    }
+
+    /**
+     * Add a non-periodic flow statistics record to the statistics history.
+     *
+     * @param history    History of flow statistics.
+     * @param newRecord  A flow statistics record to be added.
+     *                   Note that periodic flag in this record must be false.
+     * @return  A {@link FlowStatsHistory} instance if the statistics history
+     *          needs to be updated. Otherwise {@code null}.
+     */
+    public static FlowStatsHistory addNonPeriodic(
+        FlowStatsHistory history, FlowStatsRecord newRecord) {
+        assert !Boolean.TRUE.equals(newRecord.isPeriodic());
+        Long newTime = newRecord.getTime();
+        NavigableMap<Long, FlowStatsRecord> map = toNavigableMap(history);
+
+        // Determine the latest record in the given history.
         Map.Entry<Long, FlowStatsRecord> latest = map.lastEntry();
         if (latest != null) {
-            // Prevent too frequent records.
-            Long ltime = latest.getKey();
-            if (newTime.longValue() - ltime.longValue() < MIN_INTERVAL) {
+            // Don't add the given record if it is older than the latest
+            // record.
+            Long latestTime = latest.getKey();
+            long ltime = latestTime.longValue();
+            long time = newTime.longValue();
+            if (time <= ltime) {
+                return null;
+            }
+
+            if (time - ltime < MIN_INTERVAL) {
+                // The interval between the latest record and the given
+                // record is too short.
                 FlowStatsRecord lfsr = latest.getValue();
                 if (Boolean.TRUE.equals(lfsr.isPeriodic())) {
                     // Don't remove periodic record.
                     return null;
                 }
-                map.remove(ltime);
+
+                // Overwrite the latest record.
+                map.remove(latestTime);
             }
         }
 
-        // Return a new statistics history.
         map.put(newTime, newRecord);
         List<FlowStatsRecord> list = new ArrayList<>(map.values());
         return new FlowStatsHistoryBuilder().
             setFlowStatsRecord(list).build();
     }
+
+    /**
+     * Create a key string which identifies the transaction for getting flow
+     * statistics from switch.
+     *
+     * @param nid  A MD-SAL node identifier which specifies the switch.
+     * @param xid  A OpenFlow transaction ID.
+     * @return  A key string which identifies the read transaction.
+     */
+    public static String createTransactionKey(String nid, BigInteger xid) {
+        return new StringBuilder(xid.toString()).
+            append('@').append(nid).toString();
+    }
 }
index 3059d3c35511f04a2ed5adb96f4e74088d96c8b0..7af4e2f6a8b6a164c32fc6f982f783dcf579555d 100644 (file)
@@ -119,19 +119,19 @@ public final class FlowUtils {
     /**
      * A flow cookie which contains the {@link #VTN_FLOW_COOKIE} bits.
      */
-    private static final FlowCookie  COOKIE_VTN =
+    public static final FlowCookie  COOKIE_VTN =
         new FlowCookie(NumberUtils.getUnsigned(VTN_FLOW_COOKIE));
 
     /**
      * A flow cookie mask which specifies all bits in the cookie.
      */
-    private static final FlowCookie  COOKIE_MASK_ALL =
+    public static final FlowCookie  COOKIE_MASK_ALL =
         new FlowCookie(NumberUtils.getUnsigned(-1L));
 
     /**
      * A flow cookie mask which specifies the {@link #VTN_FLOW_COOKIE} bits.
      */
-    private static final FlowCookie  COOKIE_MASK_VTN =
+    public static final FlowCookie  COOKIE_MASK_VTN =
         new FlowCookie(NumberUtils.getUnsigned(VTN_FLOW_COOKIE_MASK));
 
     /**
@@ -355,6 +355,28 @@ public final class FlowUtils {
         return 0;
     }
 
+    /**
+     * Compare the given two {@link Duration} instances.
+     *
+     * @param d1  The first instance to be compared.
+     * @param d2  The second instance to be compared.
+     * @return  A negative integer, zero, or a positive integer as the first
+     *          instance is less than, equal to, or greater than the second
+     *          instance.
+     */
+    public static int compare(Duration d1, Duration d2) {
+        long s1 = MiscUtils.longValue(d1.getSecond());
+        long s2 = MiscUtils.longValue(d2.getSecond());
+        int ret = Long.compare(s1, s2);
+        if (ret == 0) {
+            long n1 = MiscUtils.longValue(d1.getNanosecond());
+            long n2 = MiscUtils.longValue(d2.getNanosecond());
+            ret = Long.compare(n1, n2);
+        }
+
+        return ret;
+    }
+
     /**
      * Convert the given {@link VirtualRoute} instances into a list of
      * {@link VNodeRoute} instances.
@@ -723,19 +745,37 @@ public final class FlowUtils {
      * @param snode   A {@link SalNode} instance which specifies the target
      *                switch.
      * @param vfent   A {@link VtnFlowEntry} instance.
-     * @return  A {@link RemoveFlowInput} instance on success.
-     *          {@code null} if the target node is not present.
+     * @return  A {@link RemoveFlowInput} instance.
      */
     public static RemoveFlowInput createRemoveFlowInput(
         SalNode snode, VtnFlowEntry vfent) {
-        Short table = vfent.getTableId();
+        Uri uri = createTxUri(vfent, "remove-flow:");
+        return createRemoveFlowInput(snode, vfent, uri);
+    }
+
+    /**
+     * Construct an RPC input to uninstall the specified MD-SAL flow entry.
+     *
+     * <p>
+     *   Note that the caller must ensure that the specified switch is present.
+     * </p>
+     *
+     * @param snode  A {@link SalNode} instance which specifies the target
+     *               switch.
+     * @param flow   A {@link Flow} instance.
+     * @param uri    A {@link Uri} to be assigned to the input.
+     * @return  A {@link RemoveFlowInput} instance.
+     */
+    public static RemoveFlowInput createRemoveFlowInput(
+        SalNode snode, Flow flow, Uri uri) {
+        Short table = flow.getTableId();
         FlowTableRef tref =
             new FlowTableRef(snode.getFlowTableIdentifier(table));
 
-        return new RemoveFlowInputBuilder((Flow)vfent).
+        return new RemoveFlowInputBuilder(flow).
             setNode(snode.getNodeRef()).
             setFlowTable(tref).
-            setTransactionUri(createTxUri(vfent, "remove-flow:")).
+            setTransactionUri(uri).
             setCookieMask(COOKIE_MASK_ALL).
             setStrict(true).
             setBarrier(true).
diff --git a/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/util/flow/GetFlowStatsRpc.java b/manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/util/flow/GetFlowStatsRpc.java
new file mode 100644 (file)
index 0000000..3f186d1
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2015 NEC Corporation
+ * All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.vtn.manager.internal.util.flow;
+
+import java.math.BigInteger;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+
+import org.opendaylight.vtn.manager.VTNException;
+
+import org.opendaylight.vtn.manager.internal.util.rpc.RpcInvocation;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetFlowStatisticsFromFlowTableInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetFlowStatisticsFromFlowTableOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.OpendaylightFlowStatisticsService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.TransactionId;
+
+/**
+ * {@code GetFlowStatsRpc} describes an invocation of
+ * get-flow-statistics-from-flow-table RPC provided by the MD-SAL flow
+ * statistics service.
+ */
+public final class GetFlowStatsRpc
+    extends RpcInvocation<GetFlowStatisticsFromFlowTableInput,
+                          GetFlowStatisticsFromFlowTableOutput> {
+    /**
+     * Issue a get-flow-statistics-from-flow-table RPC request.
+     *
+     * @param fss  MD-SAL flow statistics service.
+     * @param in   The RPC input.
+     */
+    public GetFlowStatsRpc(OpendaylightFlowStatisticsService fss,
+                           GetFlowStatisticsFromFlowTableInput in) {
+        super(in, fss.getFlowStatisticsFromFlowTable(in));
+    }
+
+    /**
+     * Wait for the RPC execution to complete, and return the transaction ID
+     * returned by the RPC.
+     *
+     * @param timeout  The maximum time to wait.
+     * @param unit     The time unit of the {@code timeout} argument.
+     * @param logger   A {@link Logger} instance.
+     * @return  The transaction ID associated with the read transaction.
+     * @throws VTNException  An error occurred.
+     */
+    public BigInteger getTransactionId(long timeout, TimeUnit unit,
+                                       Logger logger) throws VTNException {
+        GetFlowStatisticsFromFlowTableOutput result =
+            getResult(timeout, unit, logger);
+        if (result == null) {
+            String msg = "Flow stats RPC did not return the result";
+            logger.error("{}: {}", getName(), msg);
+            throw new VTNException(msg);
+        }
+
+        TransactionId txId = result.getTransactionId();
+        if (txId == null) {
+            String msg = "Flow stats RPC did not return XID";
+            logger.error("{}: {}: {}", getName(), msg, result);
+            throw new VTNException(msg);
+        }
+
+        return txId.getValue();
+    }
+
+    // RpcInvocation
+
+    /**
+     * Return the name of the RPC.
+     *
+     * @return  "get-flow-stats".
+     */
+    @Override
+    public String getName() {
+        return "get-flow-stats";
+    }
+}
index 98edde6f6b6a1b97c07ba547ad43dea8eeeef5a0..c3487723cd575d211790d081dea141bf2fa724e0 100644 (file)
@@ -1,5 +1,4 @@
-
-/**
+/*
  * Copyright (c) 2015 NEC Corporation
  * All rights reserved.
  *
@@ -7,6 +6,7 @@
  * terms of the Eclipse Public License v1.0 which accompanies this
  * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
  */
+
 package org.opendaylight.vtn.manager.internal;
 
 import org.junit.Assert;
@@ -15,17 +15,14 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.inventory.rev15020
 import org.opendaylight.controller.sal.core.NodeConnector;
 import org.opendaylight.vtn.manager.internal.util.inventory.SalPort;
 
-
 /**
- * JUnit test for {@ SpecificPortFilter}
+ * JUnit test for {@link SpecificPortFilter}
  */
-
 public class SpecificPortFilterTest extends TestBase {
- /**
-   * Test method for
-   * {@link SpecificPortFilter#accepts(NodeConnecter,VtnPort)}
-   */
-
+    /**
+     * Test method for
+     * {@link SpecificPortFilter#accept(NodeConnector,VtnPort)}
+     */
     @Test
     public void testAccept() {
         SpecificPortFilter specificPortFilter = null;
index 24a187b4a53f566f9cb2a0320dde1b5ed6443913..3a7b086ac9c52e8ea0e13f71770cb6640572b0f5 100644 (file)
@@ -26,7 +26,6 @@ import org.slf4j.LoggerFactory;
 /**
  * JUnit test for {@link OperationalListener}.
  */
-
 public class OperationalListenerTest extends TestBase {
 
      /**
@@ -97,7 +96,7 @@ public class OperationalListenerTest extends TestBase {
 
     /**
      * Test method for
-     * {@link OperationalListener#awaitConfig()}.
+     * {@link OperationalListener#awaitConfig(long)}.
      */
     @Test
     public void awaitConfigTest() {
@@ -115,7 +114,7 @@ public class OperationalListenerTest extends TestBase {
 
     /**
      * Test method for
-     * {@link OperationalListener#enterEvent()}.
+     * {@link OperationalListener#enterEvent(AsyncDataChangeEvent)}.
      */
     @Test
     public void enterEventTest() {
@@ -124,7 +123,7 @@ public class OperationalListenerTest extends TestBase {
 
     /**
      * Test method for
-     * {@link OperationalListener#exitEvent()}.
+     * {@link OperationalListener#exitEvent(Void)}.
      */
     @Test
     public void exitEventTest() {
@@ -133,7 +132,7 @@ public class OperationalListenerTest extends TestBase {
 
     /**
      * Test method for
-     * {@link OperationalListener#onCreated()}.
+     * {@link OperationalListener#onCreated(Void,IdentifiedData)}.
      */
     @Test
     public void onCreatedTest() {
@@ -152,7 +151,7 @@ public class OperationalListenerTest extends TestBase {
 
     /**
      * Test method for
-     * {@link OperationalListener#onUpdated()}.
+     * {@link OperationalListener#onUpdated(Void,ChangedData)}.
      */
     @Test
     public void onUpdatedTest() {
@@ -171,7 +170,7 @@ public class OperationalListenerTest extends TestBase {
 
     /**
      * Test method for
-     * {@link OperationalListener#onRemoved()}.
+     * {@link OperationalListener#onRemoved(Void,IdentifiedData)}.
      */
     @Test
     public void onRemovedTest() {
index 2e477a502266c9c81bbfa1366b7858929ffeefde..3207443bc7a70497672d3980a2b53a23640907e1 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015 NEC Corporation
  * All rights reserved.
  *
 
 package org.opendaylight.vtn.manager.internal.inventory;
 
-import org.junit.Test;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+import java.util.Map;
+
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.opendaylight.vtn.manager.internal.TestBase;
-import org.mockito.Mockito;
-import org.opendaylight.vtn.manager.internal.VTNManagerProvider;
-import org.opendaylight.controller.md.sal.binding.api.DataBroker;
-import org.opendaylight.yangtools.yang.binding.DataObject;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.opendaylight.vtn.manager.internal.TxQueue;
+import org.opendaylight.vtn.manager.internal.TxTask;
 import org.opendaylight.vtn.manager.internal.util.ChangedData;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.vtn.manager.internal.util.IdentifiedData;
-import org.opendaylight.vtn.manager.internal.TxQueue;
-import org.opendaylight.vtn.manager.internal.util.tx.TxQueueImpl;
 import org.opendaylight.vtn.manager.internal.util.inventory.SalPort;
+
+import org.opendaylight.vtn.manager.internal.TestBase;
+
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
-
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
 
 /**
- * JUnit test for {@link NodeConnectorListener}
- *
- * NodeConnectorListener test class is to test Listener class listens
- * the change of MD-SAL node connectors or not.
+ * JUnit test for {@link NodeConnectorListener}.
  */
-
 public class NodeConnectorListenerTest extends TestBase{
     /**
-     * Logger instance.
-     */
-    private static final Logger  LOG = LoggerFactory.getLogger(NodeConnectorListenerTest.class);
-    /**
-     * create a mock object for DataBroker class
-     */
-    DataBroker broker = Mockito.mock(DataBroker.class);
-    /**
-     * create a mock object for VTNManagerProvider class
-     */
-    VTNManagerProvider provider = Mockito.mock(VTNManagerProvider.class);
-    /**
-     * create a mock object for DataObject class
-     */
-    DataObject dataObject = Mockito.mock(DataObject.class);
-    /**
-     * create a mock object for FlowCapableNodeConnector class
-     */
-    FlowCapableNodeConnector fCapableNC = Mockito.mock(FlowCapableNodeConnector.class);
-    /**
-     * create a mock object for AsyncDataChangeEvent class
-     */
-    AsyncDataChangeEvent asyncDataChangeEvent = Mockito.mock(AsyncDataChangeEvent.class);
-    /**
-     * create a object for TxQueue
+     * Mock-up of {@link TxQueue}.
      */
-    TxQueue txQueue = new TxQueueImpl("QUEUE1", provider);
+    private TxQueue  txQueue;
+
     /**
-     * create a object for NodeConnectorListener
+     * Mock-up of {@link DataBroker}.
      */
-    NodeConnectorListener nodeConnectorListener = new NodeConnectorListener(txQueue, broker);
+    private DataBroker  dataBroker;
+
     /**
-     * create a object for PortUpdateTask
+     * Mock-up of {@link ListenerRegistration}.
      */
-    PortUpdateTask portUpdateTask = new PortUpdateTask(LOG);
+    private ListenerRegistration  registration;
+
     /**
-     * create a object for InstanceIdentifier
+     * A {@link NodeConnectorListener} instance for test.
      */
-    InstanceIdentifier<FlowCapableNodeConnector> data = InstanceIdentifier.builder(Nodes.class).child(Node.class).child(NodeConnector.class).augmentation(FlowCapableNodeConnector.class).build();
+    private NodeConnectorListener  ncListener;
 
-    Long nodeId = 100L;
-    Long portId = 200L;
     /**
-     * create a object for SalPort
+     * Set up test environment.
      */
-    SalPort salPort = new SalPort(nodeId, portId, "port address");
+    @Before
+    public void setUp() {
+        dataBroker = mock(DataBroker.class);
+        txQueue = mock(TxQueue.class);
+        registration = mock(ListenerRegistration.class);
+
+        when(dataBroker.registerDataChangeListener(
+                 any(LogicalDatastoreType.class), any(InstanceIdentifier.class),
+                 any(NodeConnectorListener.class), any(DataChangeScope.class))).
+            thenReturn(registration);
+        ncListener = new NodeConnectorListener(txQueue, dataBroker);
+    }
 
     /**
-     * Test method for
-     * {@link NodeConnectorListener#getLogger}.
+     * Test case for
+     * {@link NodeConnectorListener#NodeConnectorListener(TxQueue, DataBroker)}.
      */
     @Test
-    public void testGetLogger() {
-
-        assertTrue(nodeConnectorListener.getLogger() instanceof Logger);
+    public void testConstructor() {
+        LogicalDatastoreType oper = LogicalDatastoreType.OPERATIONAL;
+        DataChangeScope scope = DataChangeScope.SUBTREE;
+        InstanceIdentifier<FlowCapableNodeConnector> path = getPath();
+        verify(dataBroker).
+            registerDataChangeListener(oper, path, ncListener, scope);
+        verifyZeroInteractions(registration);
+        verifyZeroInteractions(txQueue);
+        assertEquals(FlowCapableNodeConnector.class,
+                     ncListener.getTargetType());
     }
 
     /**
-     * Test method for
-     * {@link NodeConnectorListener#onRemoved}.
+     * Test case for
+     * {@link NodeConnectorListener#enterEvent(AsyncDataChangeEvent)}.
      */
     @Test
-    public void testOnRemoved() {
-        try {
-            portUpdateTask.addUpdated(data, salPort);
-            IdentifiedData identifiedData = new IdentifiedData(data, fCapableNC);
-            nodeConnectorListener.onRemoved(portUpdateTask, identifiedData);
-        } catch (Exception e) {
-            assertTrue(e instanceof ClassCastException);
-        }
+    public void testEnterEvent() {
+        AsyncDataChangeEvent ev = null;
+        PortUpdateTask task = ncListener.enterEvent(ev);
+        assertTrue(task instanceof PortUpdateTask);
+        PortUpdateTask task1 = ncListener.enterEvent(ev);
+        assertTrue(task1 instanceof PortUpdateTask);
+        assertNotSame(task, task1);
     }
 
+
     /**
-     * Test method for
-     * {@link NodeConnectorListener#enterEvent}.
+     * Test case for {@link NodeConnectorListener#exitEvent(PortUpdateTask)}.
      */
     @Test
-    public void testEnterEvent() {
-        try {
-            assertTrue(nodeConnectorListener.enterEvent(asyncDataChangeEvent) instanceof PortUpdateTask);
-        } catch (Exception e) {
-            assertTrue(e instanceof Exception);
-        }
+    public void testExitEvent() {
+        reset(txQueue);
+        Logger logger = mock(Logger.class);
+        PortUpdateTask task = new PortUpdateTask(logger);
+        ncListener.exitEvent(task);
+        verifyZeroInteractions(txQueue);
+
+        SalPort sport = new SalPort(123L, 45L);
+        InstanceIdentifier<FlowCapableNodeConnector> path = InstanceIdentifier.
+            builder(Nodes.class).
+            child(Node.class, sport.getNodeKey()).
+            child(NodeConnector.class, sport.getNodeConnectorKey()).
+            augmentation(FlowCapableNodeConnector.class).
+            build();
+        task.addUpdated(path, sport);
+        ncListener.exitEvent(task);
+        verify(txQueue, never()).postFirst(any(TxTask.class));
+        verify(txQueue).post(task);
+        verifyZeroInteractions(logger);
     }
 
     /**
-     * Test method for
-     * {@link NodeConnectorListener#onUpdated}.
+     * Test case for
+     * {@link NodeConnectorListener#onCreated(PortUpdateTask,IdentifiedData)}.
      */
     @Test
-    public void testOnUpdated() {
-        try {
-            portUpdateTask.addUpdated(data, salPort);
-            ChangedData changedData = new ChangedData(data, fCapableNC, fCapableNC);
-            nodeConnectorListener.onUpdated(portUpdateTask, changedData);
-        } catch (Exception e) {
-            assertTrue(e instanceof ClassCastException);
+    public void testOnCreated() {
+        reset(txQueue);
+        FlowCapableNodeConnector fcnc = mock(FlowCapableNodeConnector.class);
+
+        Logger logger = mock(Logger.class);
+        PortUpdateTask task = new PortUpdateTask(logger);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of unsupported port.
+        NodeId unsupportedNode = new NodeId("unknown:1");
+        NodeConnectorId unsupportedPort = new NodeConnectorId("unknown:1:2");
+        InstanceIdentifier<FlowCapableNodeConnector> path = InstanceIdentifier.
+            builder(Nodes.class).
+            child(Node.class, new NodeKey(unsupportedNode)).
+            child(NodeConnector.class, new NodeConnectorKey(unsupportedPort)).
+            augmentation(FlowCapableNodeConnector.class).
+            build();
+        IdentifiedData<FlowCapableNodeConnector> data =
+            new IdentifiedData<>(path, fcnc);
+        ncListener.onCreated(task, data);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of OpenFlow logical port.
+        NodeId nid = new NodeId("openflow:1");
+        NodeConnectorId logical = new NodeConnectorId("openflow:1:CONTROLLER");
+        path = InstanceIdentifier.builder(Nodes.class).
+            child(Node.class, new NodeKey(nid)).
+            child(NodeConnector.class, new NodeConnectorKey(logical)).
+            augmentation(FlowCapableNodeConnector.class).
+            build();
+        data = new IdentifiedData<>(path, fcnc);
+        ncListener.onCreated(task, data);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of OpenFlow physical port.
+        SalPort[] ports = {
+            new SalPort(1L, 1L),
+            new SalPort(-1L, 0xffffff00L),
+            new SalPort(123L, 456L),
+        };
+        Map<SalPort, InstanceIdentifier<FlowCapableNodeConnector>> updated =
+            new HashMap<>();
+        for (SalPort sport: ports) {
+            path = InstanceIdentifier.builder(Nodes.class).
+                child(Node.class, sport.getNodeKey()).
+                child(NodeConnector.class, sport.getNodeConnectorKey()).
+                augmentation(FlowCapableNodeConnector.class).
+                build();
+            assertEquals(null, updated.put(sport, path));
+            data = new IdentifiedData<>(path, fcnc);
+            ncListener.onCreated(task, data);
+            assertEquals(true, task.hasUpdates());
         }
+
+        verifyZeroInteractions(txQueue);
+        assertEquals(ports.length, updated.size());
+        assertEquals(updated, task.getUpdatedMap());
     }
 
     /**
-     * Test method for
-     * {@link NodeConnectorListener#onCreate}.
+     * Test case for
+     * {@link NodeConnectorListener#onUpdated(PortUpdateTask,ChangedData)}.
      */
     @Test
-    public void testOnCretaed() {
-
-        try {
-            portUpdateTask.addUpdated(data, salPort);
-            IdentifiedData identifiedData = new IdentifiedData(data, fCapableNC);
-            nodeConnectorListener.onCreated(portUpdateTask, identifiedData);
-        } catch (Exception e) {
-            assertTrue(e instanceof ClassCastException);
+    public void testOnUpdated() {
+        reset(txQueue);
+        FlowCapableNodeConnector fcnc1 = mock(FlowCapableNodeConnector.class);
+        FlowCapableNodeConnector fcnc2 = mock(FlowCapableNodeConnector.class);
+
+        Logger logger = mock(Logger.class);
+        PortUpdateTask task = new PortUpdateTask(logger);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of unsupported port.
+        NodeId unsupportedNode = new NodeId("unknown:1");
+        NodeConnectorId unsupportedPort = new NodeConnectorId("unknown:1:2");
+        InstanceIdentifier<FlowCapableNodeConnector> path = InstanceIdentifier.
+            builder(Nodes.class).
+            child(Node.class, new NodeKey(unsupportedNode)).
+            child(NodeConnector.class, new NodeConnectorKey(unsupportedPort)).
+            augmentation(FlowCapableNodeConnector.class).
+            build();
+        ChangedData<FlowCapableNodeConnector> data =
+            new ChangedData<>(path, fcnc1, fcnc2);
+        ncListener.onUpdated(task, data);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of OpenFlow logical port.
+        NodeId nid = new NodeId("openflow:1");
+        NodeConnectorId logical = new NodeConnectorId("openflow:1:CONTROLLER");
+        path = InstanceIdentifier.builder(Nodes.class).
+            child(Node.class, new NodeKey(nid)).
+            child(NodeConnector.class, new NodeConnectorKey(logical)).
+            augmentation(FlowCapableNodeConnector.class).
+            build();
+        data = new ChangedData<>(path, fcnc1, fcnc2);
+        ncListener.onUpdated(task, data);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of OpenFlow physical port.
+        SalPort[] ports = {
+            new SalPort(1L, 1L),
+            new SalPort(-1L, 0xffffff00L),
+            new SalPort(123L, 456L),
+        };
+        Map<SalPort, InstanceIdentifier<FlowCapableNodeConnector>> updated =
+            new HashMap<>();
+        for (SalPort sport: ports) {
+            path = InstanceIdentifier.builder(Nodes.class).
+                child(Node.class, sport.getNodeKey()).
+                child(NodeConnector.class, sport.getNodeConnectorKey()).
+                augmentation(FlowCapableNodeConnector.class).
+                build();
+            assertEquals(null, updated.put(sport, path));
+            data = new ChangedData<>(path, fcnc1, fcnc2);
+            ncListener.onUpdated(task, data);
+            assertEquals(true, task.hasUpdates());
         }
+
+        verifyZeroInteractions(txQueue);
+        assertEquals(ports.length, updated.size());
+        assertEquals(updated, task.getUpdatedMap());
     }
 
     /**
-     * Test method for
-     * {@link NodeConnectorListener#exitEvent}.
+     * Test case for
+     * {@link NodeConnectorListener#onRemoved(PortUpdateTask,IdentifiedData)}.
      */
     @Test
-    public void testExitEvent() {
+    public void testOnRemoved() {
+        reset(txQueue);
+        FlowCapableNodeConnector fcnc = mock(FlowCapableNodeConnector.class);
 
-        try {
-            nodeConnectorListener.exitEvent(portUpdateTask);
-        } catch (Exception e) {
-            assertTrue(e instanceof Exception);
-        }
+        Logger logger = mock(Logger.class);
+        PortUpdateTask task = new PortUpdateTask(logger);
+        assertEquals(false, task.hasUpdates());
 
-        try {
-            portUpdateTask.addUpdated(data, salPort);
-            nodeConnectorListener.exitEvent(portUpdateTask);
-        } catch (Exception e) {
-            assertTrue(e instanceof Exception);
+        // In case of unsupported port.
+        NodeId unsupportedNode = new NodeId("unknown:1");
+        NodeConnectorId unsupportedPort = new NodeConnectorId("unknown:1:2");
+        InstanceIdentifier<FlowCapableNodeConnector> path = InstanceIdentifier.
+            builder(Nodes.class).
+            child(Node.class, new NodeKey(unsupportedNode)).
+            child(NodeConnector.class, new NodeConnectorKey(unsupportedPort)).
+            augmentation(FlowCapableNodeConnector.class).
+            build();
+        IdentifiedData<FlowCapableNodeConnector> data =
+            new IdentifiedData<>(path, fcnc);
+        ncListener.onRemoved(task, data);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of OpenFlow logical port.
+        NodeId nid = new NodeId("openflow:1");
+        NodeConnectorId logical = new NodeConnectorId("openflow:1:CONTROLLER");
+        path = InstanceIdentifier.builder(Nodes.class).
+            child(Node.class, new NodeKey(nid)).
+            child(NodeConnector.class, new NodeConnectorKey(logical)).
+            augmentation(FlowCapableNodeConnector.class).
+            build();
+        data = new IdentifiedData<>(path, fcnc);
+        ncListener.onRemoved(task, data);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of OpenFlow physical port.
+        SalPort[] ports = {
+            new SalPort(1L, 1L),
+            new SalPort(-1L, 0xffffff00L),
+            new SalPort(123L, 456L),
+        };
+        Map<SalPort, InstanceIdentifier<FlowCapableNodeConnector>> updated =
+            new HashMap<>();
+        for (SalPort sport: ports) {
+            path = InstanceIdentifier.builder(Nodes.class).
+                child(Node.class, sport.getNodeKey()).
+                child(NodeConnector.class, sport.getNodeConnectorKey()).
+                augmentation(FlowCapableNodeConnector.class).
+                build();
+            assertEquals(null, updated.put(sport, path));
+            data = new IdentifiedData<>(path, fcnc);
+            ncListener.onRemoved(task, data);
+            assertEquals(true, task.hasUpdates());
         }
 
+        verifyZeroInteractions(txQueue);
+        assertEquals(ports.length, updated.size());
+        assertEquals(updated, task.getUpdatedMap());
     }
 
     /**
-     * Test method for
-     * {@link NodeConnectorListener#getWildcardPath}.
+     * Test case for {@link NodeConnectorListener#getWildcardPath()}.
      */
     @Test
     public void testGetWildcardPath() {
+        assertEquals(getPath(), ncListener.getWildcardPath());
+    }
+
+    /**
+     * Test case for {@link NodeConnectorListener#getLogger()}.
+     */
+    @Test
+    public void testGetLogger() {
+        Logger logger = ncListener.getLogger();
+        assertEquals(NodeConnectorListener.class.getName(), logger.getName());
+    }
 
-        nodeConnectorListener.getWildcardPath();
+    /**
+     * Test case for {@link NodeConnectorListener#close()}.
+     */
+    @Test
+    public void testClose() {
+        verifyZeroInteractions(registration);
+        ncListener.close();
+        verify(registration).close();
+    }
 
+    /**
+     * Return a wildcard path to the MD-SAL data model to listen.
+     */
+    private InstanceIdentifier<FlowCapableNodeConnector> getPath() {
+        return InstanceIdentifier.builder(Nodes.class).
+            child(Node.class).
+            child(NodeConnector.class).
+            augmentation(FlowCapableNodeConnector.class).
+            build();
     }
 }
index 142b20802c8e470406df079d7a9ee37dbfba6428..22f1d701f526d263976b47f753082ec5de64d0b6 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015 NEC Corporation
  * All rights reserved.
  *
 
 package org.opendaylight.vtn.manager.internal.inventory;
 
-import org.junit.Test;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.opendaylight.vtn.manager.internal.TxQueue;
+import org.opendaylight.vtn.manager.internal.TxTask;
+import org.opendaylight.vtn.manager.internal.util.ChangedData;
+import org.opendaylight.vtn.manager.internal.util.IdentifiedData;
+import org.opendaylight.vtn.manager.internal.util.inventory.SalNode;
+
 import org.opendaylight.vtn.manager.internal.TestBase;
-import org.mockito.Mockito;
-import org.opendaylight.vtn.manager.internal.VTNManagerProvider;
+
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.vtn.manager.internal.util.IdentifiedData;
-import org.opendaylight.vtn.manager.internal.TxQueue;
-import org.opendaylight.vtn.manager.internal.util.tx.TxQueueImpl;
-import org.opendaylight.vtn.manager.internal.util.inventory.SalPort;
-import org.opendaylight.vtn.manager.internal.util.ChangedData;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.types.rev150209.VtnUpdateType;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
 
 /**
- * JUnit test for {@link NodeListener}
- *
- * NodeListener test class is to test Listener class listens
- * the change of MD-SAL nodes or not.
+ * JUnit test for {@link NodeListener}.
  */
-
-public class NodeListenerTest extends TestBase{
-
-    /**
-     * Logger instance.
-     */
-    private static final Logger  LOG = LoggerFactory.getLogger(NodeListenerTest.class);
+public class NodeListenerTest extends TestBase {
     /**
-     * create a mock object for DataBroker class
+     * Mock-up of {@link TxQueue}.
      */
-    DataBroker broker = Mockito.mock(DataBroker.class);
-    /**
-     * create a mock object for VTNManagerProvider class
-     */
-    VTNManagerProvider provider = Mockito.mock(VTNManagerProvider.class);
+    private TxQueue  txQueue;
+
     /**
-     * create a mock object for FlowCapableNode class
+     * Mock-up of {@link DataBroker}.
      */
-    FlowCapableNode fCapableNode = Mockito.mock(FlowCapableNode.class);
+    private DataBroker  dataBroker;
+
     /**
-     * create a mock object for AsyncDataChangeEvent class
-     */
-    AsyncDataChangeEvent asyncDataChangeEvent = Mockito.mock(AsyncDataChangeEvent.class);
-     /**
-     * create a  object for TxQueue
+     * Mock-up of {@link ListenerRegistration}.
      */
-    TxQueue txQueue = new TxQueueImpl("QUEUE1", provider);
+    private ListenerRegistration  registration;
+
     /**
-     * create a  object for NodeListener
+     * A {@link NodeListener} instance for test.
      */
-    NodeListener nodeListener = new NodeListener(txQueue, broker);
+    private NodeListener  nodeListener;
+
     /**
-     * create a  object for NodeUpdateTask
+     * Set up test environment.
      */
-    NodeUpdateTask nodeUpdateTask = new NodeUpdateTask(LOG);
+    @Before
+    public void setUp() {
+        dataBroker = mock(DataBroker.class);
+        txQueue = mock(TxQueue.class);
+        registration = mock(ListenerRegistration.class);
+
+        when(dataBroker.registerDataChangeListener(
+                 any(LogicalDatastoreType.class), any(InstanceIdentifier.class),
+                 any(NodeListener.class), any(DataChangeScope.class))).
+            thenReturn(registration);
+        nodeListener = new NodeListener(txQueue, dataBroker);
+    }
+
     /**
-     * create a  object for InstanceIdentifier
+     * Test case for {@link NodeListener#NodeListener(TxQueue, DataBroker)}.
      */
-    InstanceIdentifier<FlowCapableNode> instanceIdentifier = InstanceIdentifier.builder(Nodes.class).child(Node.class).augmentation(FlowCapableNode.class).build();
+    @Test
+    public void testConstructor() {
+        LogicalDatastoreType oper = LogicalDatastoreType.OPERATIONAL;
+        DataChangeScope scope = DataChangeScope.SUBTREE;
+        InstanceIdentifier<FlowCapableNode> path = getPath();
+        verify(dataBroker).
+            registerDataChangeListener(oper, path, nodeListener, scope);
+        verifyZeroInteractions(registration);
+        verify(txQueue).postFirst(any(TxTask.class));
+        verify(txQueue, never()).post(any(TxTask.class));
+        assertEquals(FlowCapableNode.class, nodeListener.getTargetType());
+    }
 
-    Long nodeId = 100L;
-    Long portId = 200L;
     /**
-     * create a  object for SalPort
+     * Test case for {@link NodeListener#enterEvent(AsyncDataChangeEvent)}.
      */
-    SalPort salPort = new SalPort(nodeId, portId, "port address");
+    @Test
+    public void testEnterEvent() {
+        AsyncDataChangeEvent ev = null;
+        NodeUpdateTask task = nodeListener.enterEvent(ev);
+        assertTrue(task instanceof NodeUpdateTask);
+        NodeUpdateTask task1 = nodeListener.enterEvent(ev);
+        assertTrue(task1 instanceof NodeUpdateTask);
+        assertNotSame(task, task1);
+    }
 
     /**
-     * Test method for
-     * {@link NodeListener#getLogger}.
+     * Test case for {@link NodeListener#exitEvent(NodeUpdateTask)}.
      */
     @Test
-    public void testGetLogger() {
+    public void testExitEvent() {
+        reset(txQueue);
+        Logger logger = mock(Logger.class);
+        NodeUpdateTask task = new NodeUpdateTask(logger);
+        nodeListener.exitEvent(task);
+        verifyZeroInteractions(txQueue);
 
-        assertTrue(nodeListener.getLogger() instanceof Logger);
+        SalNode snode = new SalNode(123L);
+        InstanceIdentifier<FlowCapableNode> path = InstanceIdentifier.
+            builder(Nodes.class).
+            child(Node.class, snode.getNodeKey()).
+            augmentation(FlowCapableNode.class).
+            build();
+        task.addUpdated(path, snode);
+        nodeListener.exitEvent(task);
+        verify(txQueue, never()).postFirst(any(TxTask.class));
+        verify(txQueue).post(task);
+        verifyZeroInteractions(logger);
     }
 
     /**
-     * Test method for
-     * {@link NodeListener#onRemoved}.
+     * Test case for {@link NodeListener#onCreated(NodeUpdateTask,IdentifiedData)}.
      */
     @Test
-    public void testOnRemoved() {
-        try {
-            nodeUpdateTask.addUpdated(instanceIdentifier, salPort);
-            IdentifiedData identifiedData = new IdentifiedData(instanceIdentifier, fCapableNode);
-            nodeListener.onRemoved(nodeUpdateTask, identifiedData);
-        } catch (Exception e) {
-            assertTrue(e instanceof ClassCastException);
+    public void testOnCreated() {
+        reset(txQueue);
+        FlowCapableNode fcn = mock(FlowCapableNode.class);
+
+        Logger logger = mock(Logger.class);
+        NodeUpdateTask task = new NodeUpdateTask(logger);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of unsupported node.
+        NodeId unsupported = new NodeId("unknown:1");
+        InstanceIdentifier<FlowCapableNode> path = InstanceIdentifier.
+            builder(Nodes.class).
+            child(Node.class, new NodeKey(unsupported)).
+            augmentation(FlowCapableNode.class).
+            build();
+        IdentifiedData<FlowCapableNode> data = new IdentifiedData<>(path, fcn);
+        nodeListener.onCreated(task, data);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of OpenFlow node.
+        SalNode[] nodes = {
+            new SalNode(1L),
+            new SalNode(-1L),
+            new SalNode(12345L),
+        };
+        Map<SalNode, InstanceIdentifier<FlowCapableNode>> updated =
+            new HashMap<>();
+        for (SalNode snode: nodes) {
+            path = InstanceIdentifier.builder(Nodes.class).
+                child(Node.class, snode.getNodeKey()).
+                augmentation(FlowCapableNode.class).
+                build();
+            assertEquals(null, updated.put(snode, path));
+            data = new IdentifiedData<>(path, fcn);
+            nodeListener.onCreated(task, data);
+            assertEquals(true, task.hasUpdates());
         }
+
+        verifyZeroInteractions(txQueue);
+        assertEquals(nodes.length, updated.size());
+        assertEquals(updated, task.getUpdatedMap());
     }
 
-     /**
-     * Test method for
-     * {@link NodeListener#onUpdated}.
+    /**
+     * Test case for {@link NodeListener#onUpdated(NodeUpdateTask,ChangedData)}.
      */
     @Test
     public void testOnUpdated() {
+        reset(txQueue);
+        FlowCapableNode fcn1 = mock(FlowCapableNode.class);
+        FlowCapableNode fcn2 = mock(FlowCapableNode.class);
+        SalNode snode = new SalNode(12345L);
+        InstanceIdentifier<FlowCapableNode> path = InstanceIdentifier.
+            builder(Nodes.class).
+            child(Node.class, snode.getNodeKey()).
+            augmentation(FlowCapableNode.class).
+            build();
+        ChangedData<FlowCapableNode> data =
+            new ChangedData<>(path, fcn1, fcn2);
+
+        Logger logger = mock(Logger.class);
+        NodeUpdateTask task = new NodeUpdateTask(logger);
+        assertEquals(false, task.hasUpdates());
+
         try {
-            nodeUpdateTask.addUpdated(instanceIdentifier, salPort);
-            ChangedData changedData = new ChangedData(instanceIdentifier, fCapableNode, fCapableNode);
-            nodeListener.onUpdated(nodeUpdateTask, changedData);
-        } catch (Exception e) {
-            assertTrue(e instanceof Exception);
+            nodeListener.onUpdated(task, data);
+            unexpected();
+        } catch (IllegalStateException e) {
         }
+
+        assertEquals(false, task.hasUpdates());
+        verifyZeroInteractions(txQueue);
     }
 
     /**
-     * Test method for
-     * {@link NodeListener#onCreated}.
+     * Test case for {@link NodeListener#onRemoved(NodeUpdateTask,IdentifiedData)}.
      */
     @Test
-    public void testOnCreated() {
-        try {
-            nodeUpdateTask.addUpdated(instanceIdentifier, salPort);
-            IdentifiedData identifiedData = new IdentifiedData(instanceIdentifier, fCapableNode);
-            nodeListener.onCreated(nodeUpdateTask, identifiedData);
-        } catch (Exception e) {
-            assertTrue(e instanceof ClassCastException);
+    public void testOnRemoved() {
+        reset(txQueue);
+        FlowCapableNode fcn = mock(FlowCapableNode.class);
+
+        Logger logger = mock(Logger.class);
+        NodeUpdateTask task = new NodeUpdateTask(logger);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of unsupported node.
+        NodeId unsupported = new NodeId("unknown:1");
+        InstanceIdentifier<FlowCapableNode> path = InstanceIdentifier.
+            builder(Nodes.class).
+            child(Node.class, new NodeKey(unsupported)).
+            augmentation(FlowCapableNode.class).
+            build();
+        IdentifiedData<FlowCapableNode> data = new IdentifiedData<>(path, fcn);
+        nodeListener.onRemoved(task, data);
+        assertEquals(false, task.hasUpdates());
+
+        // In case of OpenFlow node.
+        SalNode[] nodes = {
+            new SalNode(1L),
+            new SalNode(-1L),
+            new SalNode(12345L),
+        };
+        Map<SalNode, InstanceIdentifier<FlowCapableNode>> updated =
+            new HashMap<>();
+        for (SalNode snode: nodes) {
+            path = InstanceIdentifier.builder(Nodes.class).
+                child(Node.class, snode.getNodeKey()).
+                augmentation(FlowCapableNode.class).
+                build();
+            assertEquals(null, updated.put(snode, path));
+            data = new IdentifiedData<>(path, fcn);
+            nodeListener.onRemoved(task, data);
+            assertEquals(true, task.hasUpdates());
         }
+
+        verifyZeroInteractions(txQueue);
+        assertEquals(nodes.length, updated.size());
+        assertEquals(updated, task.getUpdatedMap());
     }
 
     /**
-     * Test method for
-     * {@link NodeListener#enterEvent}.
+     * Test case for {@link NodeListener#getWildcardPath()}.
      */
     @Test
-    public void testEnterEvent() {
-        try {
-            assertTrue(nodeListener.enterEvent(asyncDataChangeEvent) instanceof NodeUpdateTask);
-        } catch (Exception e) {
-            assertTrue(e instanceof Exception);
-        }
+    public void testGetWildcardPath() {
+        assertEquals(getPath(), nodeListener.getWildcardPath());
     }
 
-     /**
-     * Test method for
-     * {@link NodeListener#exitEvent}.
+    /**
+     * Test case for {@link NodeListener#getRequiredEvents()}.
      */
     @Test
-    public void testExitEvent() {
-
-        try {
-            nodeListener.exitEvent(nodeUpdateTask);
-        } catch (Exception e) {
-            assertTrue(e instanceof Exception);
-        }
-
-        try {
-            nodeUpdateTask.addUpdated(instanceIdentifier, salPort);
-            nodeListener.exitEvent(nodeUpdateTask);
-        } catch (Exception e) {
-            assertTrue(e instanceof Exception);
-        }
-
+    public void testGetRequiredEvents() {
+        Set<VtnUpdateType> events = nodeListener.getRequiredEvents();
+        assertEquals(2, events.size());
+        assertEquals(true, events.contains(VtnUpdateType.CREATED));
+        assertEquals(true, events.contains(VtnUpdateType.REMOVED));
     }
 
     /**
-     * Test method for
-     * {@link NodeListener#getRequiredEvents}.
+     * Test case for {@link NodeListener#getLogger()}.
      */
     @Test
-    public void testGetRequiredEvents() {
+    public void testGetLogger() {
+        Logger logger = nodeListener.getLogger();
+        assertEquals(NodeListener.class.getName(), logger.getName());
+    }
 
-        nodeListener.getRequiredEvents();
+    /**
+     * Test case for {@link NodeListener#close()}.
+     */
+    @Test
+    public void testClose() {
+        verifyZeroInteractions(registration);
+        nodeListener.close();
+        verify(registration).close();
+    }
 
+    /**
+     * Return a wildcard path to the MD-SAL data model to listen.
+     */
+    private InstanceIdentifier<FlowCapableNode> getPath() {
+        return InstanceIdentifier.builder(Nodes.class).
+            child(Node.class).
+            augmentation(FlowCapableNode.class).
+            build();
     }
 }
index 37ad1f6e50a1b3b30ce02342a54a127cd0220585..c7dd2831b5628216c41c4d42cef05f5d9cbc8295 100644 (file)
@@ -8,44 +8,52 @@
  */
 package org.opendaylight.vtn.manager.internal.inventory;
 
-import org.junit.Test;
 import org.slf4j.Logger;
-import org.junit.Assert;
-import org.mockito.Mockito;
+
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.BeforeClass;
-import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.junit.Test;
+
+import org.mockito.Mockito;
+
 import org.opendaylight.vtn.manager.internal.VTNManagerImpl;
 import org.opendaylight.vtn.manager.internal.VTNManagerProvider;
+import org.opendaylight.vtn.manager.internal.util.ChangedData;
+import org.opendaylight.vtn.manager.internal.util.IdentifiedData;
 import org.opendaylight.vtn.manager.internal.util.IdentifierTargetComparator;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.types.rev150209.VtnUpdateType;
+
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
-import org.opendaylight.vtn.manager.internal.util.IdentifiedData;
-import org.opendaylight.vtn.manager.internal.util.ChangedData;
+
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.inventory.rev150209.vtn.nodes.VtnNode;
+
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.inventory.rev150209.VtnNodes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.inventory.rev150209.vtn.nodes.VtnNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.types.rev150209.VtnUpdateType;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
 
 /**
- * JUnit test for {@link VTNInventoryManager}
+ * JUnit test for {@link VTNInventoryManager}.
  */
 public class VTNInventoryManagerTest {
-
     /**
-      * Static instance of VTNInventoryManager to perform unit testing.
-      */
+     * Static instance of VTNInventoryManager to perform unit testing.
+     */
     private static VTNInventoryManager vtnInventoryManager;
+
     /**
-      * Static instance of VTNManagerProvider to perform unit testing.
-      */
+     * Static instance of VTNManagerProvider to perform unit testing.
+     */
     private static VTNManagerProvider vtnManagerProvider;
+
     /**
-      * Static instance of VTNInventoryListener to perform unit testing.
-      */
+     * Static instance of VTNInventoryListener to perform unit testing.
+     */
     private static VTNInventoryListener vtnInventoryListener;
 
     /**
@@ -79,7 +87,7 @@ public class VTNInventoryManagerTest {
 
     /**
      * Test method for
-     * {@link VTNInventoryManager#addListener()}.
+     * {@link VTNInventoryManager#addListener(VTNInventoryListener)}.
      */
     @Test
     public void testAddListener() {
@@ -128,7 +136,7 @@ public class VTNInventoryManagerTest {
 
     /**
      * Test method for
-     * {@link VTNInventoryManager#getComparatpr()}.
+     * {@link VTNInventoryManager#getComparator()}.
      */
     @Test
     public void testGetComparator() {
@@ -138,17 +146,18 @@ public class VTNInventoryManagerTest {
 
     /**
      * Test method for
-     * {@link VTNInventoryManager#getOrder()}.
+     * {@link VTNInventoryManager#getOrder(VtnUpdateType)}.
      */
     @Test
     public void testGetOrder() {
-        Assert.assertFalse(vtnInventoryManager.getOrder(VtnUpdateType.CREATED));
-        Assert.assertTrue(vtnInventoryManager.getOrder(VtnUpdateType.REMOVED));
+        Assert.assertTrue(vtnInventoryManager.getOrder(VtnUpdateType.CREATED));
+        Assert.assertTrue(vtnInventoryManager.getOrder(VtnUpdateType.CHANGED));
+        Assert.assertFalse(vtnInventoryManager.getOrder(VtnUpdateType.REMOVED));
     }
 
     /**
      * Test method for
-     * {@link VTNInventoryManager#enterEvent()}.
+     * {@link VTNInventoryManager#enterEvent(AsyncDataChangeEvent)}.
      */
     @Test
     public void testEnterEvent() {
@@ -157,7 +166,7 @@ public class VTNInventoryManagerTest {
 
     /**
      * Test method for
-     * {@link VTNInventoryManager#exitEvent()}.
+     * {@link VTNInventoryManager#exitEvent(Void)}.
      */
     @Test
     public void testExitEvent() {
@@ -170,7 +179,7 @@ public class VTNInventoryManagerTest {
 
     /**
      * Test method for
-     * {@link VTNInventoryManager#onCreated()}.
+     * {@link VTNInventoryManager#onCreated(Void,IdentifiedData)}.
      */
     @Test
     public void testOnCreated() {
@@ -183,7 +192,7 @@ public class VTNInventoryManagerTest {
 
     /**
      * Test method for
-     * {@link VTNInventoryManager#onUpdated()}.
+     * {@link VTNInventoryManager#onUpdated(Void,ChangedData)}.
      */
     @Test
     public void testOnUpdated() {
@@ -206,7 +215,7 @@ public class VTNInventoryManagerTest {
 
     /**
      * Test method for
-     * {@link VTNInventoryManager#onRemoved()}.
+     * {@link VTNInventoryManager#onRemoved(Void,IdentifiedData)}.
      */
     @Test
     public void testOnRemoved() {
index 7c1a79b26758872bc3f6f0b7d42d52006ca51b2a..d73f3ab38b20766f22cd32d100a4f1b8f0b2bdef 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015 NEC Corporation
  * All rights reserved.
  *
@@ -6,6 +6,7 @@
  * terms of the Eclipse Public License v1.0 which accompanies this
  * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
  */
+
 package org.opendaylight.vtn.manager.internal.routing;
 
 import org.opendaylight.vtn.manager.internal.TestBase;
@@ -29,6 +30,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.pathpolicy.rev150209.se
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.pathpolicy.rev150209.SetPathCostOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.types.rev150209.VtnPortDesc;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.pathpolicy.rev150209.SetPathCostInputBuilder;
+
  /**
  * SetPathCostTask test class is to test  the MD-SAL transaction task that set
  * all the specified link cost configurations into the path policy
@@ -208,7 +210,7 @@ public class SetPathCostTaskTest extends TestBase {
 
     /**
      * Test method for
-     * {@link SetPathCostTask#OnStarted()}.
+     * {@link SetPathCostTask#onStarted(TxContext)}.
      */
     @Test
     public void testOnStarted() {
@@ -235,7 +237,7 @@ public class SetPathCostTaskTest extends TestBase {
 
     /**
      * Test method for
-     * {@link SetPathCostTask#OnSuccess()}.
+     * {@link SetPathCostTask#onSuccess(VTNManagerProvider,List)}.
      */
     @Test
     public void testOnSuccess() {
index 8b1ed92cc3468110ab9b4b8dd907eb3362338039..126cbad61189cb08f75720e0313c3a4215d656fb 100644 (file)
@@ -6,6 +6,7 @@
  * terms of the Eclipse Public License v1.0 which accompanies this
  * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
  */
+
 package org.opendaylight.vtn.manager.internal.routing;
 
 import org.junit.Test;
@@ -50,7 +51,7 @@ import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.vtn.manager.internal.util.IdentifiedData;
 
 /**
- * JUnit test for {@link VTNRoutingManager}
+ * JUnit test for {@link VTNRoutingManager}.
  */
 public class VTNRoutingManagerTest {
     /**
@@ -145,7 +146,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#addListener()}.
+     * {@link VTNRoutingManager#addListener(VTNRoutingListener)}.
      */
     @Test
     public void testAddListener() {
@@ -158,7 +159,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#getRouteResolver()}.
+     * {@link VTNRoutingManager#getRouteResolver(Integer)}.
      */
     @Test
     public void testGetRouteResolver() {
@@ -180,7 +181,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#enterEvent()}.
+     * {@link VTNRoutingManager#enterEvent(AsyncDataChangeEvent)}.
      */
     @Test
     public void testEnterEvent() {
@@ -189,7 +190,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#exitEvent()}.
+     * {@link VTNRoutingManager#exitEvent(TopologyEventContext)}.
      */
     @Test
     public void testExitEvent() {
@@ -203,7 +204,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#onCreated()}.
+     * {@link VTNRoutingManager#onCreated(TopologyEventContext,IdentifiedData)}.
      */
     @Test
     public void testOnCreated() {
@@ -218,7 +219,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#onUpdated()}.
+     * {@link VTNRoutingManager#onUpdated(TopologyEventContext,ChangedData)}.
      */
     @Test
     public void testOnUpdated() {
@@ -231,7 +232,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#onRemoved()}.
+     * {@link VTNRoutingManager#onRemoved(TopologyEventContext,IdentifiedData)}.
      */
     @Test
     public void testOnRemoved() {
@@ -277,7 +278,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#initConfig()}.
+     * {@link VTNRoutingManager#initConfig(boolean)}.
      */
     @Test
     public void testInitConfig() {
@@ -291,7 +292,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#initRpcService()}.
+     * {@link VTNRoutingManager#initRpcServices(RpcProviderRegistry,CompositeAutoCloseable)}.
      */
     @Test
     public void testInitRpcServices() {
@@ -304,7 +305,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#setPathPolicy()}.
+     * {@link VTNRoutingManager#setPathPolicy(SetPathPolicyInput)}.
      */
     @Test
     public void testSetPathPolicy() {
@@ -319,7 +320,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#removePathPolicy()}.
+     * {@link VTNRoutingManager#removePathPolicy(RemovePathPolicyInput)}.
      */
     @Test
     public void testRemovePathPolicy() {
@@ -334,7 +335,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#setPathCost()}.
+     * {@link VTNRoutingManager#setPathCost(SetPathCostInput)}.
      */
     @Test
     public void testSetPathCost() {
@@ -349,7 +350,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#removePathCost()}.
+     * {@link VTNRoutingManager#removePathCost(RemovePathCostInput)}.
      */
     @Test
     public void testRemovePathCost() {
@@ -364,7 +365,7 @@ public class VTNRoutingManagerTest {
 
     /**
      * Test method for
-     * {@link VTNRoutingManager#clearPathCost()}.
+     * {@link VTNRoutingManager#clearPathPolicy()}.
      */
     @Test
     public void testClearPathPolicy() {
index 4a3995e425574640e3691d160e6fd645b32e6ae1..f654c673241e7ba280635b26bd98f26478bf1f81 100644 (file)
@@ -38,11 +38,11 @@ public class FutureCancellerTest extends TestBase {
 
     /**
      * Test method for
-     * {@link FutureCanceller#Set()}.
+     * {@link FutureCanceller#set(Timer,long,ListenableFuture)} and
+     * {@link FutureCanceller#set(Timer,long,ListenableFuture,boolean)}.
      */
     @Test
     public void testSet() {
-
         listenableFuture = SettableFuture.create();
         FutureCanceller.set(timer, 10L, listenableFuture);
         FutureCanceller.set(timer, 0, listenableFuture);
index c1f65bcbe3f998e64ec553ec39f5c4c4566b1d2a..fff4ddb78afdab213470a15626d9cb53a6a3f4be 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015 NEC Corporation
  * All rights reserved.
  *
@@ -82,27 +82,19 @@ public class VTNFutureTaskTest extends TestBase {
 
     /**
      * Test method for
-     * {@link VTNFutureTask#AddListener()}.
-     *
+     * {@link VTNFutureTask#addListener(Runnable,Executor)}.
      */
     @Test
     public void testAddListener() {
-
-    /**
-     * creating runnable object used for test.
-     *
-     */
+        // creating runnable object used for test.
         RunnableThread listener = new RunnableThread(1);
-    /**
-     * creating executor object used for test.
-     *
-     */
+
+        // creating executor object used for test.
         Executor executor = new SequentialExecutor();
         int i = 2;
         VTNFutureTask vtnFutureTask = new VTNFutureTask(listener, i);
 
         vtnFutureTask.addListener(listener, executor);
-
     }
 
     /**
@@ -125,7 +117,7 @@ public class VTNFutureTaskTest extends TestBase {
 
     /**
      * Test method for
-     * {@link VTNFutureTask#CheckedGet(long,timeunit)}.
+     * {@link VTNFutureTask#checkedGet(long,TimeUnit)}.
      */
     @Test
     public void testcheckedGetinTime() {
index 4be52f3ae62bb9754d780f8517cf37c2865d1a71..271284cbb3d8067fee043c387c9b7a89597f4dd3 100644 (file)
@@ -22,7 +22,6 @@ import org.opendaylight.vtn.manager.internal.TestBase;
  * JUnit test for {@link VTNThreadPool}
  */
 public class VTNThreadPoolTest extends TestBase {
-
     /**
      * task Thread class used for test.
      * this task wait until a latch is counted down to zero.
@@ -107,23 +106,23 @@ public class VTNThreadPoolTest extends TestBase {
     }
 
     /**
-     * Test method for
-     * {@link VTNThreadPool#VTNThreadPool(String, int, long)}.
+     * Test method for {@link VTNThreadPool#VTNThreadPool(String)}.
      *
      * <p>
-     * This also tests
-     * {@link VTNThreadPool#execute(Runnable)},
-     * {@link VTNThreadPool#executeTask(Runnable)},
-     * {@link VTNThreadPool#shutdown()},
-     * {@link VTNThreadPool#terminate()}.
+     *   This also tests the following methods.
      * </p>
+     * <ul>
+     *   <li>{@link VTNThreadPool#execute(Runnable)}</li>
+     *   <li>{@link VTNThreadPool#executeTask(Runnable)}</li>
+     *   <li>{@link VTNThreadPool#shutdown()}</li>
+     *   <li>{@link VTNThreadPool#terminate()}</li>
+     * </ul>
      */
     @Test
-    public void testVTNThreadPool() {
-
+    public void testSingleThreadPool() {
         // 1 task on 1 worker.
         CountDownLatch latch = new CountDownLatch(1);
-        VTNThreadPool pool = new VTNThreadPool("test", 1, 1000L);
+        VTNThreadPool pool = new VTNThreadPool("test");
         ThreadTask task1 = new ThreadTask(10000L, latch);
         pool.execute(task1);
         assertTrue(task1.await(10L, TimeUnit.SECONDS));
@@ -148,12 +147,29 @@ public class VTNThreadPoolTest extends TestBase {
         assertFalse(task2.await(100L, TimeUnit.MILLISECONDS));
 
         pool.terminate();
+    }
 
+    /**
+     * Test method for
+     * {@link VTNThreadPool#VTNThreadPool(String, int, long)}.
+     *
+     * <p>
+     *   This also tests the following methods.
+     * </p>
+     * <ul>
+     *   <li>{@link VTNThreadPool#execute(Runnable)}</li>
+     *   <li>{@link VTNThreadPool#executeTask(Runnable)}</li>
+     *   <li>{@link VTNThreadPool#shutdown()}</li>
+     *   <li>{@link VTNThreadPool#terminate()}</li>
+     * </ul>
+     */
+    @Test
+    public void testVTNThreadPool() {
         // 2 tasks on 2 workers.
-        pool = new VTNThreadPool("test2", 2, 1000L);
-        latch = new CountDownLatch(2);
-        task1 = new ThreadTask(1000L, latch);
-        task2 = new ThreadTask(1000L, latch);
+        VTNThreadPool pool = new VTNThreadPool("test2", 2, 1000L);
+        CountDownLatch latch = new CountDownLatch(2);
+        ThreadTask task1 = new ThreadTask(1000L, latch);
+        ThreadTask task2 = new ThreadTask(1000L, latch);
 
         assertTrue(pool.executeTask(task1));
         assertFalse(task1.await(500L, TimeUnit.MILLISECONDS));
@@ -202,8 +218,8 @@ public class VTNThreadPoolTest extends TestBase {
 
         // in case one thread throw Exception.
         pool = new VTNThreadPool("test_exception", 1, 10000L);
-        latch1 = new CountDownLatch(1);
-        latch2 = new CountDownLatch(1);
+        CountDownLatch latch1 = new CountDownLatch(1);
+        CountDownLatch latch2 = new CountDownLatch(1);
         ExpThreadTask etask = new ExpThreadTask(1000L, latch1);
         task1 = new ThreadTask(10000L, latch2);
 
@@ -312,7 +328,6 @@ public class VTNThreadPoolTest extends TestBase {
 
         assertFalse(pool.join(1L));
 
-
         // in case join is interrupted.
         pool = new VTNThreadPool("test3", 2, 10000L);
 
diff --git a/manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/util/flow/FlowStatsUtilsTest.java b/manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/util/flow/FlowStatsUtilsTest.java
new file mode 100644 (file)
index 0000000..627fa87
--- /dev/null
@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 2015 NEC Corporation
+ * All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.vtn.manager.internal.util.flow;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static org.opendaylight.vtn.manager.internal.util.flow.FlowUtilsTest.VTN_FLOW_COOKIE;
+import static org.opendaylight.vtn.manager.internal.util.flow.FlowUtilsTest.createDuration;
+import static org.opendaylight.vtn.manager.internal.util.flow.FlowUtilsTest.createVtnFlowEntry;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.Random;
+import java.util.Set;
+
+import org.junit.Test;
+
+import org.opendaylight.vtn.manager.util.NumberUtils;
+
+import org.opendaylight.vtn.manager.internal.util.inventory.SalNode;
+import org.opendaylight.vtn.manager.internal.util.inventory.SalPort;
+
+import org.opendaylight.vtn.manager.internal.TestBase;
+
+import org.opendaylight.controller.md.sal.binding.api.ReadTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.flow.rev150410.VtnFlowId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.VtnFlows;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.tenant.flow.info.VtnDataFlow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.tenant.flow.info.VtnDataFlowKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.FlowStatsHistory;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.FlowStatsHistoryBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.VtnFlowEntry;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecord;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.data.flow.fields.flow.stats.history.FlowStatsRecordBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.flows.VtnFlowTable;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.impl.flow.rev150313.vtn.flows.VtnFlowTableKey;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowStatisticsData;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetFlowStatisticsFromFlowTableInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.flow.statistics.FlowStatistics;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.flow.statistics.FlowStatisticsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.statistics.types.rev130925.GenericStatistics;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.statistics.types.rev130925.duration.DurationBuilder;
+
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.Counter32;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.Counter64;
+
+/**
+ * JUnit test for {@link FlowStatsUtils}.
+ */
+public class FlowStatsUtilsTest extends TestBase {
+    /**
+     * Test case for
+     * {@link FlowStatsUtils#getIdentifier(String,VtnDataFlowKey)}.
+     */
+    @Test
+    public void testGetIdentifier1() {
+        String[] tenants = {"vtn1", "vtn_2", "tenant_3"};
+        VtnFlowId[] flowIds = {
+            new VtnFlowId(BigInteger.valueOf(1L)),
+            new VtnFlowId(BigInteger.valueOf(1234567L)),
+            new VtnFlowId(BigInteger.valueOf(0xfffffffffL)),
+        };
+
+        for (String tname: tenants) {
+            VtnFlowTableKey key = new VtnFlowTableKey(tname);
+            for (VtnFlowId id: flowIds) {
+                VtnDataFlowKey vdfKey = new VtnDataFlowKey(id);
+                InstanceIdentifier<FlowStatsHistory> exp = InstanceIdentifier.
+                    builder(VtnFlows.class).
+                    child(VtnFlowTable.class, key).
+                    child(VtnDataFlow.class, vdfKey).
+                    child(FlowStatsHistory.class).
+                    build();
+                assertEquals(exp, FlowStatsUtils.getIdentifier(tname, vdfKey));
+            }
+        }
+    }
+
+    /**
+     * Test case for
+     * {@link FlowStatsUtils#getIdentifier(NodeId,FlowId)}.
+     */
+    @Test
+    public void testGetIdentifier2() {
+        NodeId[] nodes = {
+            new NodeId("openflow:1"),
+            new NodeId("openflow:7777"),
+            new NodeId("openflow:18446744073709551615"),
+        };
+        FlowId[] flowIds = {
+            new FlowId("flow-1"),
+            new FlowId("flow_2"),
+            new FlowId("flow3"),
+        };
+
+        TableKey tableKey = new TableKey((short)0);
+        for (NodeId node: nodes) {
+            NodeKey nodeKey = new NodeKey(node);
+            for (FlowId id: flowIds) {
+                InstanceIdentifier<FlowStatistics> exp = InstanceIdentifier.
+                    builder(Nodes.class).
+                    child(Node.class, nodeKey).
+                    augmentation(FlowCapableNode.class).
+                    child(Table.class, tableKey).
+                    child(Flow.class, new FlowKey(id)).
+                    augmentation(FlowStatisticsData.class).
+                    child(FlowStatistics.class).
+                    build();
+                assertEquals(exp, FlowStatsUtils.getIdentifier(node, id));
+            }
+        }
+    }
+
+    /**
+     * Test case for
+     * {@link FlowStatsUtils#createGetFlowStatsInput(VtnFlowEntry)}.
+     *
+     * @throws Exception  An error occurred.
+     */
+    @Test
+    public void testCreateGetFlowStatsInput1() throws Exception {
+        long flowId = 9999999L;
+        int order = 0;
+        SalPort ingress = new SalPort(12L, 345L);
+        Short table = 0;
+        VtnFlowEntry vfent = createVtnFlowEntry(flowId, order, ingress);
+        FlowCookie cookieMask = new FlowCookie(NumberUtils.getUnsigned(-1L));
+
+        GetFlowStatisticsFromFlowTableInput input =
+            FlowStatsUtils.createGetFlowStatsInput(vfent);
+        assertEquals(ingress.getNodeRef(), input.getNode());
+        assertEquals(vfent.getPriority(), input.getPriority());
+        assertEquals(table, input.getTableId());
+        assertEquals(vfent.getIdleTimeout(), input.getIdleTimeout());
+        assertEquals(vfent.getHardTimeout(), input.getHardTimeout());
+        assertEquals(vfent.getCookie(), input.getCookie());
+        assertEquals(cookieMask, input.getCookieMask());
+        assertEquals(vfent.getMatch(), input.getMatch());
+        assertEquals(vfent.getFlags(), input.getFlags());
+        assertEquals(vfent.getInstructions(), input.getInstructions());
+        assertEquals(null, input.isStrict());
+        assertEquals(null, input.isBarrier());
+        assertEquals(null, input.getOutPort());
+        assertEquals(null, input.getOutGroup());
+        assertEquals(null, input.getBufferId());
+        assertEquals(null, input.getContainerName());
+        assertEquals(null, input.getFlowName());
+        assertEquals(null, input.isInstallHw());
+    }
+
+    /**
+     * Test case for {@link FlowStatsUtils#createGetFlowStatsInput(SalNode)}.
+     */
+    @Test
+    public void testCreateGetFlowStatsInput2() throws Exception {
+        SalNode[] nodes = {
+            new SalNode(1L),
+            new SalNode(7777L),
+            new SalNode(-1L),
+        };
+
+        FlowCookie cookie =
+            new FlowCookie(NumberUtils.getUnsigned(VTN_FLOW_COOKIE));
+        FlowCookie cookieMask =
+            new FlowCookie(NumberUtils.getUnsigned(0xffff000000000000L));
+        Short table = 0;
+
+        for (SalNode snode: nodes) {
+            GetFlowStatisticsFromFlowTableInput input =
+                FlowStatsUtils.createGetFlowStatsInput(snode);
+            assertEquals(snode.getNodeRef(), input.getNode());
+            assertEquals(null, input.getPriority());
+            assertEquals(table, input.getTableId());
+            assertEquals(null, input.getIdleTimeout());
+            assertEquals(null, input.getHardTimeout());
+            assertEquals(cookie, input.getCookie());
+            assertEquals(cookieMask, input.getCookieMask());
+            assertEquals(null, input.getMatch());
+            assertEquals(null, input.getFlags());
+            assertEquals(null, input.getInstructions());
+            assertEquals(null, input.isStrict());
+            assertEquals(null, input.isBarrier());
+            assertEquals(null, input.getOutPort());
+            assertEquals(null, input.getOutGroup());
+            assertEquals(null, input.getBufferId());
+            assertEquals(null, input.getContainerName());
+            assertEquals(null, input.getFlowName());
+            assertEquals(null, input.isInstallHw());
+        }
+    }
+
+    /**
+     * Test case for {@link FlowStatsUtils#read(ReadTransaction,NodeId,FlowId)}.
+     *
+     * @throws Exception  An error occurred.
+     */
+    @Test
+    public void testRead() throws Exception {
+        ReadTransaction rtx = mock(ReadTransaction.class);
+        LogicalDatastoreType oper = LogicalDatastoreType.OPERATIONAL;
+        NodeId nodeId = new NodeId("openflow:12345");
+        FlowId flowId = new FlowId("md-flow:55");
+        InstanceIdentifier<FlowStatistics> path = InstanceIdentifier.
+            builder(Nodes.class).
+            child(Node.class, new NodeKey(nodeId)).
+            augmentation(FlowCapableNode.class).
+            child(Table.class, new TableKey((short)0)).
+            child(Flow.class, new FlowKey(flowId)).
+            augmentation(FlowStatisticsData.class).
+            child(FlowStatistics.class).
+            build();
+
+        // In case where the target MD-SAL flow statistics is not present.
+        FlowStatistics fstats = null;
+        when(rtx.read(oper, path)).thenReturn(getReadResult(fstats));
+        assertEquals(null, FlowStatsUtils.read(rtx, nodeId, flowId));
+        verify(rtx).read(oper, path);
+
+        // In case whether the target MD-SAL flow statistics is present.
+        reset(rtx);
+        fstats = new FlowStatisticsBuilder().build();
+        when(rtx.read(oper, path)).thenReturn(getReadResult(fstats));
+        assertEquals(fstats, FlowStatsUtils.read(rtx, nodeId, flowId));
+        verify(rtx).read(oper, path);
+    }
+
+    /**
+     * Test case for {@link FlowStatsUtils#check(GenericStatistics)}.
+     */
+    @Test
+    public void testCheck() {
+        GenericStatistics gstats = null;
+        assertEquals("flow statistics is null.", FlowStatsUtils.check(gstats));
+
+        FlowStatisticsBuilder builder = new FlowStatisticsBuilder();
+        gstats = builder.build();
+        assertEquals("No packet count.", FlowStatsUtils.check(gstats));
+
+        Counter64 packets = new Counter64(NumberUtils.getUnsigned(1234567L));
+        gstats = builder.setPacketCount(packets).build();
+        assertEquals("No byte count.", FlowStatsUtils.check(gstats));
+
+        Counter64 bytes = new Counter64(NumberUtils.getUnsigned(2345678L));
+        gstats = builder.setByteCount(packets).build();
+        assertEquals("No duration.", FlowStatsUtils.check(gstats));
+
+        DurationBuilder db = new DurationBuilder();
+        gstats = builder.setDuration(db.build()).build();
+        assertEquals("No second in duration.", FlowStatsUtils.check(gstats));
+
+        Counter32 sec = new Counter32(99999L);
+        gstats = builder.setDuration(db.setSecond(sec).build()).build();
+        assertEquals("No nanosecond in duration.",
+                     FlowStatsUtils.check(gstats));
+
+        Counter32 nsec = new Counter32(888888888L);
+        gstats = builder.setDuration(db.setNanosecond(nsec).build()).build();
+        assertEquals(null, FlowStatsUtils.check(gstats));
+    }
+
+    /**
+     * Test case for {@link FlowStatsUtils#toNavigableMap(FlowStatsHistory)}.
+     */
+    @Test
+    public void testToNavigableMap() {
+        FlowStatsHistory history = null;
+        NavigableMap<Long, FlowStatsRecord> map =
+            FlowStatsUtils.toNavigableMap(history);
+        assertEquals(0, map.size());
+
+        history = new FlowStatsHistoryBuilder().build();
+        map = FlowStatsUtils.toNavigableMap(history);
+        assertEquals(0, map.size());
+
+        Long time1 = 1L;
+        Long time2 = 2L;
+        Long time3 = 3L;
+        FlowStatsRecord fsr1 = new FlowStatsRecordBuilder().
+            setTime(time1).setDuration(createDuration(1L, 100L)).build();
+        FlowStatsRecord fsr2 = new FlowStatsRecordBuilder().
+            setTime(time2).setDuration(createDuration(2L, 200L)).build();
+        FlowStatsRecord fsr3 = new FlowStatsRecordBuilder().
+            setTime(time3).setDuration(createDuration(3L, 300L)).build();
+        List<FlowStatsRecord> records = new ArrayList<>();
+        Collections.addAll(records, fsr2, fsr3, fsr1);
+        history = new FlowStatsHistoryBuilder().
+            setFlowStatsRecord(records).build();
+        map = FlowStatsUtils.toNavigableMap(history);
+        assertEquals(3, map.size());
+        assertEquals(fsr1, map.get(time1));
+        assertEquals(fsr2, map.get(time2));
+        assertEquals(fsr3, map.get(time3));
+    }
+
+    /**
+     * Test case for
+     * {@link FlowStatsUtils#addPeriodic(FlowStatsHistory,Long,GenericStatistics)}.
+     */
+    @Test
+    public void testAddPeriodic() {
+        // In case where statistics history is not present.
+        FlowStatsHistory history = null;
+        GenericStatistics gstats = createStatistics(100L, 199L, 1L, 1234567L);
+        Long time = 9999L;
+        List<FlowStatsRecord> expected = new ArrayList<>();
+        expected.add(new FlowStatsRecordBuilder(gstats).setTime(time).
+                     setPeriodic(true).build());
+        FlowStatsHistory result =
+            FlowStatsUtils.addPeriodic(history, time, gstats);
+        assertEquals(expected, result.getFlowStatsRecord());
+
+        history = new FlowStatsHistoryBuilder().build();
+        result = FlowStatsUtils.addPeriodic(history, time, gstats);
+        assertEquals(expected, result.getFlowStatsRecord());
+
+        // Ensure that old entries are removed.
+        time = 100000000L;
+        long expire = time.longValue() - 60000L;
+        Boolean[] booleans = {Boolean.TRUE, Boolean.FALSE};
+        for (Boolean periodic: booleans) {
+            gstats = createStatistics(333L, 4444L, 100L, 55555555L);
+            FlowStatsRecord fsr1 = new FlowStatsRecordBuilder(gstats).
+                setTime(expire).setPeriodic(true).build();
+            gstats = createStatistics(350L, 4555L, 110L, 55556666L);
+            FlowStatsRecord fsr2 = new FlowStatsRecordBuilder(gstats).
+                setTime(expire + 10000L).setPeriodic(true).build();
+
+            long latestSec = 113L;
+            long latestNsec = 55556666L;
+            gstats = createStatistics(1234L, 56788L, latestSec, latestNsec);
+            FlowStatsRecord latest = new FlowStatsRecordBuilder(gstats).
+                setTime(expire + 13000L).setPeriodic(periodic).build();
+
+            gstats = createStatistics(300L, 3000L, 99L, 55556666L);
+            FlowStatsRecord old1 = new FlowStatsRecordBuilder(gstats).
+                setTime(expire - 10000L).setPeriodic(true).build();
+            FlowStatsRecord old2 = new FlowStatsRecordBuilder(gstats).
+                setTime(expire - 1L).build();
+
+            List<FlowStatsRecord> records = new ArrayList<>();
+            Collections.addAll(records, fsr2, old2, latest, old1, fsr1);
+            history = new FlowStatsHistoryBuilder().
+                setFlowStatsRecord(records).build();
+            gstats = createStatistics(1245L, 57890L, 120L, 55556677L);
+            FlowStatsRecord fsr = new FlowStatsRecordBuilder(gstats).
+                setTime(time).setPeriodic(true).build();
+            expected.clear();
+            Collections.addAll(expected, fsr1, fsr2, latest, fsr);
+            result = FlowStatsUtils.addPeriodic(history, time, gstats);
+            assertEquals(expected, result.getFlowStatsRecord());
+
+            if (!periodic.booleanValue()) {
+                // In case where the latest history is newer than the
+                // statistics to be added.
+                fsr = new FlowStatsRecordBuilder(latest).
+                    setTime(time).setKey(null).setPeriodic(true).build();
+                expected.clear();
+                Collections.addAll(expected, fsr1, fsr2, fsr);
+                gstats = createStatistics(1234L, 56787L, latestSec,
+                                          latestNsec - 1);
+                result = FlowStatsUtils.addPeriodic(history, time, gstats);
+                assertEquals(expected, result.getFlowStatsRecord());
+            }
+        }
+    }
+
+    /**
+     * Test case for
+     * {@link FlowStatsUtils#addNonPeriodic(FlowStatsHistory,FlowStatsRecord)}.
+     */
+    @Test
+    public void testAddNonPeriodic() {
+        // In case where statistics history is not present.
+        FlowStatsHistory history = null;
+        GenericStatistics gstats = createStatistics(100L, 199L, 1L, 1234567L);
+        Long time = 10000000L;
+        FlowStatsRecord fsr = new FlowStatsRecordBuilder(gstats).
+            setTime(time).build();
+        List<FlowStatsRecord> expected = new ArrayList<>();
+        expected.add(fsr);
+        FlowStatsHistory result = FlowStatsUtils.addNonPeriodic(history, fsr);
+        assertEquals(expected, result.getFlowStatsRecord());
+
+        history = new FlowStatsHistoryBuilder().build();
+        result = FlowStatsUtils.addNonPeriodic(history, fsr);
+        assertEquals(expected, result.getFlowStatsRecord());
+
+        long latestTime = time - 10000L;
+        long[] shortIntervals = {1L, 2L, 100L, 999L};
+        long[] intervals = {1000L, 1001L, 2000L, 5555L};
+        Boolean[] booleans = {Boolean.TRUE, Boolean.FALSE};
+        for (Boolean periodic: booleans) {
+            gstats = createStatistics(100L, 200L, 4000L, 0L);
+            FlowStatsRecord fsr1 = new FlowStatsRecordBuilder(gstats).
+                setTime(time - 11000L).build();
+            gstats = createStatistics(111L, 222L, 5000L, 10L);
+            FlowStatsRecord latest = new FlowStatsRecordBuilder(gstats).
+                setTime(latestTime).setPeriodic(periodic).build();
+            List<FlowStatsRecord> records = new ArrayList<>();
+            Collections.addAll(records, latest, fsr1);
+            history = new FlowStatsHistoryBuilder().
+                setFlowStatsRecord(records).build();
+
+            // Too old records should be ignored.
+            for (long l = 0; l <= 10L; l++) {
+                fsr = new FlowStatsRecordBuilder(gstats).
+                    setTime(latestTime - l).build();
+                assertEquals(null,
+                             FlowStatsUtils.addNonPeriodic(history, fsr));
+            }
+
+            gstats = createStatistics(111L, 222L, 5001L, 1111L);
+            for (long l: shortIntervals) {
+                fsr = new FlowStatsRecordBuilder(gstats).
+                    setTime(latestTime + l).build();
+                result = FlowStatsUtils.addNonPeriodic(history, fsr);
+                if (periodic.booleanValue()) {
+                    // Too frequent record should be ignored.
+                    assertEquals(null, result);
+                } else {
+                    // Too frequent record should overwrite the latest
+                    // non-periodic record.
+                    expected.clear();
+                    Collections.addAll(expected, fsr1, fsr);
+                    assertEquals(expected, result.getFlowStatsRecord());
+                }
+            }
+
+            for (long l: intervals) {
+                fsr = new FlowStatsRecordBuilder(gstats).
+                    setTime(latestTime + l).build();
+                expected.clear();
+                Collections.addAll(expected, fsr1, latest, fsr);
+                result = FlowStatsUtils.addNonPeriodic(history, fsr);
+                assertEquals(expected, result.getFlowStatsRecord());
+            }
+        }
+    }
+
+    /**
+     * Test case for
+     * {@link FlowStatsUtils#createTransactionKey(String,BigInteger)}.
+     */
+    @Test
+    public void testCreateTransactionKey() {
+        Random rand = new Random();
+        Set<String> nodeIds = new HashSet<>();
+        nodeIds.add("openflow:1");
+        nodeIds.add("openflow:18446744073709551615");
+        do {
+            long dpid = rand.nextLong();
+            nodeIds.add("openflow:" + NumberUtils.getUnsigned(dpid));
+        } while (nodeIds.size() < 20);
+
+        Set<BigInteger> xids = new HashSet<>();
+        xids.add(BigInteger.ZERO);
+        xids.add(BigInteger.ONE);
+        do {
+            xids.add(NumberUtils.getUnsigned(rand.nextLong()));
+        } while (xids.size() < 20);
+
+        Set<String> keys = new HashSet<>();
+        for (String nid: nodeIds) {
+            for (BigInteger xid: xids) {
+                String xkey = FlowStatsUtils.createTransactionKey(nid, xid);
+                assertEquals(true, keys.add(xkey));
+            }
+        }
+
+        assertEquals(nodeIds.size() * xids.size(), keys.size());
+
+        for (String nid: nodeIds) {
+            for (BigInteger xid: xids) {
+                String xkey = FlowStatsUtils.createTransactionKey(nid, xid);
+                assertEquals(false, keys.add(xkey));
+                assertEquals(true, keys.contains(xkey));
+            }
+        }
+    }
+
+    /**
+     * Create a MD-SAL flow statistics record.
+     *
+     * @param packets  The number of transmitted packets.
+     * @param bytes    The number of transmitted bytes.
+     * @param sec      Duration in seconds.
+     * @param nsec     Duration in milliseconds.
+     */
+    private static FlowStatistics createStatistics(long packets, long bytes,
+                                                   long sec, long nsec) {
+        return new FlowStatisticsBuilder().
+            setPacketCount(new Counter64(NumberUtils.getUnsigned(packets))).
+            setByteCount(new Counter64(NumberUtils.getUnsigned(bytes))).
+            setDuration(createDuration(sec, nsec)).
+            build();
+    }
+}
index 9feee0c08303aed0667c90fe6468eb45a6671787..1e267d407b55b3fa4d2b12c1f8d5cc2bd212d99c 100644 (file)
@@ -169,11 +169,13 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.types.rev150209.VtnFlow
 import org.opendaylight.yang.gen.v1.urn.opendaylight.vtn.types.rev150209.VtnSwitchPort;
 
 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.Ordered;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowTableRef;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.Flow;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.Instructions;
@@ -198,7 +200,7 @@ public class FlowUtilsTest extends TestBase {
     /**
      * Bits in a flow cookie which identifies the VTN flows.
      */
-    private static final long  VTN_FLOW_COOKIE = 0x7f56000000000000L;
+    public static final long  VTN_FLOW_COOKIE = 0x7f56000000000000L;
 
     /**
      * An implementation of {@link VtnFlowTimeoutConfig} for test.
@@ -648,10 +650,7 @@ public class FlowUtilsTest extends TestBase {
         long duration = System.currentTimeMillis() - created;
         long sec = duration / 1000L;
         long nsec = (duration % 1000L) * 1000000L;
-        Duration d = new DurationBuilder().
-            setSecond(new Counter32(sec)).
-            setNanosecond(new Counter32(nsec)).
-            build();
+        Duration d = createDuration(sec, nsec);
         DataFlowStats dfs = new DataFlowStatsBuilder().
             setPacketCount(new Counter64(BigInteger.valueOf(packets))).
             setByteCount(new Counter64(BigInteger.valueOf(bytes))).
@@ -795,6 +794,51 @@ public class FlowUtilsTest extends TestBase {
         }
     }
 
+    /**
+     * Test case for {@link FlowUtils#compare(Duration,Duration)}.
+     */
+    @Test
+    public void testCompareDuration() {
+        Duration min = createDuration(0L, 0L);
+        Duration max = createDuration(0xffffffffL, 999999999L);
+        assertEquals(0, FlowUtils.compare(min, min));
+        assertEquals(0, FlowUtils.compare(max, max));
+        assertTrue(FlowUtils.compare(min, max) < 0);
+        assertTrue(FlowUtils.compare(max, min) > 0);
+
+        Duration d1 = createDuration(0L, 1L);
+        Duration d2 = createDuration(1L, 0L);
+        Duration d3 = createDuration(1L, 2L);
+        Duration d4 = createDuration(1L, 999999999L);
+        Duration d5 = createDuration(99L, 499999998L);
+        Duration d6 = createDuration(99L, 499999999L);
+        Duration d7 = createDuration(99L, 500000000L);
+        Duration d8 = createDuration(99L, 500000001L);
+        Duration d9 = createDuration(100L, 500000000L);
+        Duration d10 = createDuration(101L, 500000000L);
+        Duration d11 = createDuration(101L, 500000001L);
+        Duration d12 = createDuration(12345L, 888888888L);
+        Duration d13 = createDuration(12345L, 888888889L);
+        Duration d14 = createDuration(0xffffffffL, 0L);
+        Duration d15 = createDuration(0xffffffffL, 999999998L);
+        List<Duration> list = new ArrayList<>();
+        Collections.addAll(list, d10, max, d7, d1, d4, d9, min, d5, d2, d13,
+                           d15, d6, d14, d8, d11, d3, d12);
+        List<Duration> expected = new ArrayList<>();
+        Collections.addAll(expected, min, d1, d2, d3, d4, d5, d6, d7, d8, d9,
+                           d10, d11, d12, d13, d14, d15, max);
+
+        Comparator<Duration> comp = new Comparator<Duration>() {
+            @Override
+            public int compare(Duration dr1, Duration dr2) {
+                return FlowUtils.compare(dr1, dr2);
+            }
+        };
+
+        Collections.sort(list, comp);
+        assertEquals(expected, list);
+    }
+
     /**
      * Test case for {@link FlowUtils#toVNodeRoutes(List, Comparator)}.
      *
@@ -1222,7 +1266,7 @@ public class FlowUtilsTest extends TestBase {
                         build();
                     String value = String.format("%s%x-%s", prefix, c, order);
                     assertEquals(new Uri(value),
-                                 FlowUtils.createTxUri(vfent,  prefix));
+                                 FlowUtils.createTxUri(vfent, prefix));
                 }
             }
         }
@@ -1257,6 +1301,11 @@ public class FlowUtilsTest extends TestBase {
         assertEquals(Boolean.TRUE, input.isStrict());
         assertEquals(Boolean.TRUE, input.isBarrier());
         assertEquals(null, input.getOutPort());
+        assertEquals(null, input.getOutGroup());
+        assertEquals(null, input.getBufferId());
+        assertEquals(null, input.getContainerName());
+        assertEquals(null, input.getFlowName());
+        assertEquals(null, input.isInstallHw());
 
         FlowTableRef tref =
             new FlowTableRef(ingress.getFlowTableIdentifier(table));
@@ -1296,6 +1345,11 @@ public class FlowUtilsTest extends TestBase {
             assertEquals(Boolean.FALSE, input.isStrict());
             assertEquals(Boolean.TRUE, input.isBarrier());
             assertEquals(null, input.getOutPort());
+            assertEquals(null, input.getOutGroup());
+            assertEquals(null, input.getBufferId());
+            assertEquals(null, input.getContainerName());
+            assertEquals(null, input.getFlowName());
+            assertEquals(null, input.isInstallHw());
         }
     }
 
@@ -1341,6 +1395,11 @@ public class FlowUtilsTest extends TestBase {
         assertEquals(Boolean.TRUE, input.isStrict());
         assertEquals(Boolean.TRUE, input.isBarrier());
         assertEquals(null, input.getOutPort());
+        assertEquals(null, input.getOutGroup());
+        assertEquals(null, input.getBufferId());
+        assertEquals(null, input.getContainerName());
+        assertEquals(null, input.getFlowName());
+        assertEquals(null, input.isInstallHw());
 
         FlowTableRef tref =
             new FlowTableRef(ingress.getFlowTableIdentifier(table));
@@ -1396,6 +1455,11 @@ public class FlowUtilsTest extends TestBase {
         assertEquals(Boolean.TRUE, input.isStrict());
         assertEquals(Boolean.TRUE, input.isBarrier());
         assertEquals(null, input.getOutPort());
+        assertEquals(null, input.getOutGroup());
+        assertEquals(null, input.getBufferId());
+        assertEquals(null, input.getContainerName());
+        assertEquals(null, input.getFlowName());
+        assertEquals(null, input.isInstallHw());
 
         FlowTableRef tref =
             new FlowTableRef(ingress.getFlowTableIdentifier(table));
@@ -1424,7 +1488,6 @@ public class FlowUtilsTest extends TestBase {
         long c = flowId | VTN_FLOW_COOKIE;
         FlowCookie cookie = new FlowCookie(NumberUtils.getUnsigned(c));
         FlowCookie cookieMask = new FlowCookie(NumberUtils.getUnsigned(-1L));
-        VtnNode vnode = new VtnNodeBuilder().setId(snode.getNodeId()).build();
 
         RemoveFlowInput input =
             FlowUtils.createRemoveFlowInput(snode, vfent);
@@ -1441,6 +1504,11 @@ public class FlowUtilsTest extends TestBase {
         assertEquals(Boolean.TRUE, input.isStrict());
         assertEquals(Boolean.TRUE, input.isBarrier());
         assertEquals(null, input.getOutPort());
+        assertEquals(null, input.getOutGroup());
+        assertEquals(null, input.getBufferId());
+        assertEquals(null, input.getContainerName());
+        assertEquals(null, input.getFlowName());
+        assertEquals(null, input.isInstallHw());
 
         FlowTableRef tref =
             new FlowTableRef(ingress.getFlowTableIdentifier(table));
@@ -1478,6 +1546,11 @@ public class FlowUtilsTest extends TestBase {
         assertEquals(Boolean.FALSE, input.isStrict());
         assertEquals(Boolean.TRUE, input.isBarrier());
         assertEquals(null, input.getOutPort());
+        assertEquals(null, input.getOutGroup());
+        assertEquals(null, input.getBufferId());
+        assertEquals(null, input.getContainerName());
+        assertEquals(null, input.getFlowName());
+        assertEquals(null, input.isInstallHw());
 
         FlowTableRef tref =
             new FlowTableRef(snode.getFlowTableIdentifier(table));
@@ -1507,6 +1580,11 @@ public class FlowUtilsTest extends TestBase {
         assertEquals(Boolean.FALSE, input.isStrict());
         assertEquals(Boolean.TRUE, input.isBarrier());
         assertEquals(tref, input.getFlowTable());
+        assertEquals(null, input.getOutGroup());
+        assertEquals(null, input.getBufferId());
+        assertEquals(null, input.getContainerName());
+        assertEquals(null, input.getFlowName());
+        assertEquals(null, input.isInstallHw());
 
         uri = new Uri(String.format("remove-flow:OUT_PORT=%s", sport));
         assertEquals(uri, input.getTransactionUri());
@@ -1515,6 +1593,53 @@ public class FlowUtilsTest extends TestBase {
         assertEquals(portNum, input.getOutPort());
     }
 
+    /**
+     * Test case for
+     * {@link FlowUtils#createRemoveFlowInput(SalNode,Flow,Uri)}.
+     *
+     * @throws Exception  An error occurred.
+     */
+    @Test
+    public void testCreateRemoveFlowInput6() throws Exception {
+        long flowId = 0x7777777777L;
+        int order = 123;
+        SalPort ingress = new SalPort(345L, 88L);
+        SalNode snode = ingress.getSalNode();
+        Short table = 0;
+        VtnFlowEntry vfent = createVtnFlowEntry(flowId, order, ingress);
+        Flow flow = new FlowBuilder(vfent).build();
+        long c = flowId | VTN_FLOW_COOKIE;
+        FlowCookie cookie = new FlowCookie(NumberUtils.getUnsigned(c));
+        FlowCookie cookieMask = new FlowCookie(NumberUtils.getUnsigned(-1L));
+        Uri uri = new Uri("remove-flow-input-5:" + flowId);
+
+        RemoveFlowInput input =
+            FlowUtils.createRemoveFlowInput(snode, flow, uri);
+        assertEquals(ingress.getNodeRef(), input.getNode());
+        assertEquals(vfent.getPriority(), input.getPriority());
+        assertEquals(table, input.getTableId());
+        assertEquals(vfent.getIdleTimeout(), input.getIdleTimeout());
+        assertEquals(vfent.getHardTimeout(), input.getHardTimeout());
+        assertEquals(cookie, input.getCookie());
+        assertEquals(cookieMask, input.getCookieMask());
+        assertEquals(vfent.getMatch(), input.getMatch());
+        assertEquals(vfent.getFlags(), input.getFlags());
+        assertEquals(vfent.getInstructions(), input.getInstructions());
+        assertEquals(Boolean.TRUE, input.isStrict());
+        assertEquals(Boolean.TRUE, input.isBarrier());
+        assertEquals(null, input.getOutPort());
+        assertEquals(null, input.getOutGroup());
+        assertEquals(null, input.getBufferId());
+        assertEquals(null, input.getContainerName());
+        assertEquals(null, input.getFlowName());
+        assertEquals(null, input.isInstallHw());
+        assertEquals(uri, input.getTransactionUri());
+
+        FlowTableRef tref =
+            new FlowTableRef(ingress.getFlowTableIdentifier(table));
+        assertEquals(tref, input.getFlowTable());
+    }
+
     /**
      * Test case for
      * {@link FlowUtils#removeFlowIdSet(ReadWriteTransaction,InstanceIdentifier)}.
@@ -2608,8 +2733,8 @@ public class FlowUtilsTest extends TestBase {
      *                 switch port.
      * @throws Exception  An error occurred.
      */
-    private VtnFlowEntry createVtnFlowEntry(long id, int order,
-                                            SalPort ingress) throws Exception {
+    public static VtnFlowEntry createVtnFlowEntry(
+        long id, int order, SalPort ingress) throws Exception {
         SalPort egress = new SalPort(ingress.getNodeNumber(), 9999L);
         NodeId node = ingress.getNodeId();
         Integer pri = 15;
@@ -2672,4 +2797,22 @@ public class FlowUtilsTest extends TestBase {
             setInstructions(insts).
             build();
     }
+
+    /**
+     * Create a {@link Duration} instance.
+     *
+     * @param sec   The number of seconds.
+     * @param nsec  The number of nanoseconds.
+     */
+    public static Duration createDuration(Long sec, Long nsec) {
+        DurationBuilder builder = new DurationBuilder();
+        if (sec != null) {
+            builder.setSecond(new Counter32(sec));
+        }
+        if (nsec != null) {
+            builder.setNanosecond(new Counter32(nsec));
+        }
+
+        return builder.build();
+    }
 }
index dc6b071004942cc478ceb656c3acc6ab0263603a..cef4bf1093c1cb9cc3b72be9c95112c14a60a84f 100644 (file)
@@ -6,47 +6,58 @@
  * terms of the Eclipse Public License v1.0 which accompanies this
  * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
  */
+
 package org.opendaylight.vtn.manager.internal.util.tx;
 
-import org.junit.Assert;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
-import java.util.List;
-import java.util.ArrayList;
+import org.junit.experimental.categories.Category;
+
+import org.opendaylight.vtn.manager.internal.TxContext;
 import org.opendaylight.vtn.manager.internal.TxTask;
 import org.opendaylight.vtn.manager.internal.util.concurrent.VTNFuture;
 import org.opendaylight.vtn.manager.internal.util.concurrent.VTNFutureTask;
-import java.util.concurrent.Callable;
-import org.opendaylight.vtn.manager.internal.TxContext;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+
+import org.opendaylight.vtn.manager.internal.SlowTest;
 
 /**
  * JUnit test for {@link TxSyncFuture}
  */
+@Category(SlowTest.class)
 public class TxSyncFutureTest {
     /**
-      * Static instance of TxTask to perform unit testing.
-      */
+     * Static instance of TxTask to perform unit testing.
+     */
     public static TxTask<Integer> txTask;
+
     /**
-      * Static instance of VTNFuture to perform unit testing.
-      */
+     * Static instance of VTNFuture to perform unit testing.
+     */
     public static VTNFuture<Integer> vtnFuture;
+
     /**
-      * Static instance of Callable to perform unit testing.
-      */
+     * Static instance of Callable to perform unit testing.
+     */
     public static Callable<Integer> callable;
+
     /**
-      * Static instance of CompositeTxTask to perform unit testing.
-      */
+     * Static instance of CompositeTxTask to perform unit testing.
+     */
     public static CompositeTxTask compostiteTxTask;
+
     /**
-      * Static instance of TxSyncFuture to perform unit testing.
-      */
+     * Static instance of TxSyncFuture to perform unit testing.
+     */
     public static TxSyncFuture<Integer> txSyncFuture;
 
     /**
@@ -98,7 +109,7 @@ public class TxSyncFutureTest {
 
     /**
      * Test method for
-     * {@link TxSyncFuture#cancel()}.
+     * {@link TxSyncFuture#cancel(boolean)}.
      */
     @Test
     public void testCancel() {
@@ -145,7 +156,7 @@ public class TxSyncFutureTest {
     /**
      * Test method for
      * {@link TxSyncFuture#get()} and
-     * {@link TxSyncFuture#ger(long, TimeUnit}.
+     * {@link TxSyncFuture#get(long, TimeUnit)}.
      */
     @Test
     public void testGet() {
diff --git a/manager/it/ofmock/src/main/java/org/opendaylight/vtn/manager/it/ofmock/impl/FlowEntryRemover.java b/manager/it/ofmock/src/main/java/org/opendaylight/vtn/manager/it/ofmock/impl/FlowEntryRemover.java
new file mode 100644 (file)
index 0000000..f7d73d8
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015 NEC Corporation
+ * All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.vtn.manager.it.ofmock.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@code FlowEntryRemover} is used to remove flow entries that match the
+ * specified condition.
+ */
+class FlowEntryRemover implements FlowTableScanner {
+    /**
+     * A list of flow entries removed from the flow table.
+     */
+    private final List<OfMockFlowEntry>  removedFlows = new ArrayList<>();
+
+    /**
+     * Return removed flow entries.
+     *
+     * @return  A list of flow entries removed from the flow table.
+     */
+    public List<OfMockFlowEntry> getRemovedFlows() {
+        return removedFlows;
+    }
+
+    // FlowTableScanner
+
+    /**
+     * Invoked when a flow entry to be removed has been found.
+     *
+     * @param ofent  A flow entry to be removed.
+     * @return  {@code true}.
+     */
+    @Override
+    public boolean flowEntryFound(OfMockFlowEntry ofent) {
+        removedFlows.add(ofent);
+        return true;
+    }
+}
diff --git a/manager/it/ofmock/src/main/java/org/opendaylight/vtn/manager/it/ofmock/impl/FlowTableScanner.java b/manager/it/ofmock/src/main/java/org/opendaylight/vtn/manager/it/ofmock/impl/FlowTableScanner.java
new file mode 100644 (file)
index 0000000..2119b3d
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2015 NEC Corporation
+ * All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.vtn.manager.it.ofmock.impl;
+
+/**
+ * {@code FlowTableScanner} is used to scan flow entries in a flow table
+ * in a mock-up of OpenFlow switch.
+ */
+interface FlowTableScanner {
+    /**
+     * Invoked when a flow entry that satisfies the specified condition has
+     * been found.
+     *
+     * @param ofent  A flow entry.
+     * @return  {@code true} means the given flow entry should be removed from
+     *          the flow table. {@code false} means the given flow entry should
+     *          be retained.
+     */
+    boolean flowEntryFound(OfMockFlowEntry ofent);
+}
index 0bfe0564086d709bccc1a70fef7c2489b7c86962..49dbd8622f901efa4d89f54aa7d42b7db4641583 100644 (file)
@@ -58,6 +58,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.Swit
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowsStatisticsUpdateBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetAggregateFlowStatisticsFromFlowTableForAllFlowsInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetAggregateFlowStatisticsFromFlowTableForAllFlowsOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetAggregateFlowStatisticsFromFlowTableForGivenMatchInput;
@@ -68,11 +69,15 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.G
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetAllFlowsStatisticsFromAllFlowTablesOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetFlowStatisticsFromFlowTableInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetFlowStatisticsFromFlowTableOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetFlowStatisticsFromFlowTableOutputBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.OpendaylightFlowStatisticsService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.flow.and.statistics.map.list.FlowAndStatisticsMapList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.flow.and.statistics.map.list.FlowAndStatisticsMapListBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.table.statistics.rev131215.GetFlowTablesStatisticsInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.table.statistics.rev131215.GetFlowTablesStatisticsOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.table.statistics.rev131215.OpendaylightFlowTableStatisticsService;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.TransactionId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.Flow;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.RemovedReasonFlags;
@@ -122,6 +127,8 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.statistics.rev131111.
 import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.statistics.rev131111.GetMeterStatisticsInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.statistics.rev131111.GetMeterStatisticsOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.statistics.rev131111.OpendaylightMeterStatisticsService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.statistics.types.rev130925.duration.Duration;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.statistics.types.rev130925.duration.DurationBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.module.config.rev141015.NodeConfigService;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.module.config.rev141015.SetConfigInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.module.config.rev141015.SetConfigOutput;
@@ -150,6 +157,8 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.table.service.rev131026.Upd
 
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.Counter32;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.Counter64;
 
 /**
  * {@code OfNode} describes a mock-up of OpenFlow switch.
@@ -174,26 +183,24 @@ public final class OfNode
     private static final long  NODE_BUFSIZE = 256L;
 
     /**
-     * The number of packets to be notified by a FLOW_REMOVED message.
+     * The number of packets to be set in flow statistics.
      */
-    private static final long  FRM_PACKET_COUNT = 5555555L;
+    private static final long  PACKET_COUNT = 5555555L;
 
     /**
-     * The number of bytes to be notified by a FLOW_REMOVED message.
+     * The number of bytes to be set in flow statistics.
      */
-    private static final long  FRM_BYTE_COUNT = 1234567890L;
+    private static final long  BYTE_COUNT = 1234567890L;
 
     /**
-     * Duration (in seconds) of a flow entry to be notified by a FLOW_REMOVED
-     * message.
+     * Duration (in seconds) of a flow entry to be set in flow statistics.
      */
-    private static final long  FRM_DURATION_SEC = 12345L;
+    private static final long  DURATION_SEC = 12345L;
 
     /**
-     * Duration (in nanoseconds) of a flow entry to be notified by a
-     * FLOW_REMOVED message.
+     * Duration (in nanoseconds) of a flow entry to be set in flow statistics.
      */
-    private static final long  FRM_DURATION_NANOSEC = 666777888L;
+    private static final long  DURATION_NANOSEC = 666777888L;
 
     /**
      * Capabilities of the switch.
@@ -259,6 +266,128 @@ public final class OfNode
         CAPABILITIES = Collections.unmodifiableList(cap);
     }
 
+    /**
+     * A thread that notifies flow statistics.
+     */
+    private final class FlowStatsThread extends Thread
+        implements FlowTableScanner {
+        /**
+         * The transaction ID associated with the flow statistics request.
+         */
+        private final TransactionId  transactionId;
+
+        /**
+         * The condition to select flow entries to be notified.
+         */
+        private final Flow  condition;
+
+        /**
+         * A list of flow statistics.
+         */
+        private final List<FlowAndStatisticsMapList>  flowStats =
+            new ArrayList<>();
+
+        /**
+         * Construct a new instance.
+         *
+         * @param xid
+         * @param cond  The MD-SAL flow entry which specifies the condition to
+         *              select flow entries.
+         */
+        private FlowStatsThread(TransactionId xid, Flow cond) {
+            super("FlowStatsThread: xid=" + xid.getValue());
+            transactionId = xid;
+            condition = cond;
+        }
+
+        /**
+         * Convert the given flow entry into a {@link FlowAndStatisticsMapList}
+         * instance.
+         *
+         * @param ofent  The flow entry to be converted.
+         * @return  A {@link FlowAndStatisticsMapList} instance.
+         */
+        private FlowAndStatisticsMapList toFlowAndStatisticsMapList(
+            OfMockFlowEntry ofent) {
+            Duration duration = new DurationBuilder().
+                setSecond(new Counter32(DURATION_SEC)).
+                setNanosecond(new Counter32(DURATION_NANOSEC)).
+                build();
+
+            return new FlowAndStatisticsMapListBuilder().
+                setPacketCount(new Counter64(BigInteger.valueOf(PACKET_COUNT))).
+                setByteCount(new Counter64(BigInteger.valueOf(BYTE_COUNT))).
+                setDuration(duration).
+                setPriority(Integer.valueOf(ofent.getPriority())).
+                setTableId(Short.valueOf((short)ofent.getTableId())).
+                setCookie(new FlowCookie(ofent.getCookie())).
+                setMatch(ofent.getMatch()).
+                setInstructions(ofent.getInstructions()).
+                setFlags(ofent.getFlowModFlags()).
+                setIdleTimeout(Integer.valueOf(ofent.getIdleTimeout())).
+                setHardTimeout(Integer.valueOf(ofent.getHardTimeout())).
+                build();
+        }
+
+        // Runnable
+
+        /**
+         * Scan the flow table, and notify flow statistics.
+         */
+        @Override
+        public void run() {
+            try {
+                scanFlowTable(condition, this);
+
+                // Publish a flows-statistics-update notification.
+                FlowsStatisticsUpdateBuilder builder =
+                    new FlowsStatisticsUpdateBuilder();
+                builder.setId(new NodeId(nodeIdentifier)).
+                    setMoreReplies(false).
+                    setTransactionId(transactionId).
+                    setFlowAndStatisticsMapList(flowStats);
+
+                ofMockProvider.publish(builder.build());
+            } catch (RuntimeException e) {
+                BigInteger xid = transactionId.getValue();
+                LOG.error("Unexpected exception: xid=" + xid, e);
+            }
+        }
+
+        // FlowTableScanner
+
+        /**
+         * Invoked when a flow entry to be notified has been found.
+         *
+         * @param ofent  The flow entry to be notified.
+         * @return  {@code false}.
+         */
+        @Override
+        public boolean flowEntryFound(OfMockFlowEntry ofent) {
+            Duration duration = new DurationBuilder().
+                setSecond(new Counter32(DURATION_SEC)).
+                setNanosecond(new Counter32(DURATION_NANOSEC)).
+                build();
+
+            FlowAndStatisticsMapList fs = new FlowAndStatisticsMapListBuilder().
+                setPacketCount(new Counter64(BigInteger.valueOf(PACKET_COUNT))).
+                setByteCount(new Counter64(BigInteger.valueOf(BYTE_COUNT))).
+                setDuration(duration).
+                setPriority(Integer.valueOf(ofent.getPriority())).
+                setTableId(Short.valueOf((short)ofent.getTableId())).
+                setCookie(new FlowCookie(ofent.getCookie())).
+                setMatch(ofent.getMatch()).
+                setInstructions(ofent.getInstructions()).
+                setFlags(ofent.getFlowModFlags()).
+                setIdleTimeout(Integer.valueOf(ofent.getIdleTimeout())).
+                setHardTimeout(Integer.valueOf(ofent.getHardTimeout())).
+                build();
+            flowStats.add(fs);
+
+            return false;
+        }
+    }
+
     /**
      * Construct a new instance.
      *
@@ -773,21 +902,24 @@ public final class OfNode
     }
 
     /**
-     * Process a bulk flow remove request.
+     * Scan the flow table.
      *
-     * @param removed  A list to store removed flow entries.
-     * @param input    An input of this RPC.
+     * @param cond     The MD-SAL flow entry which specifies the condition to
+     *                 select flow entries.
+     * @param scanner  A {@link FlowTableScanner} instance.
+     *                 Flow entries that satisfies the condition specified by
+     *                 {@code flow} will be passed to this instance.
      */
-    private synchronized void removeFlows(List<OfMockFlowEntry> removed,
-                                          RemoveFlowInput input) {
-        Short table = input.getTableId();
-        Match match = input.getMatch();
-        BigInteger oport = input.getOutPort();
-        String out = (oport == null)
+    private synchronized void scanFlowTable(Flow cond,
+                                            FlowTableScanner scanner) {
+        Short table = cond.getTableId();
+        Match match = cond.getMatch();
+        BigInteger oport = cond.getOutPort();
+        String outPort = (oport == null)
             ? null
             : OfMockUtils.getPortIdentifier(nodeIdentifier, oport);
-        FlowCookie cookie = input.getCookie();
-        FlowCookie cookieMask = input.getCookieMask();
+        FlowCookie cookie = cond.getCookie();
+        FlowCookie cookieMask = cond.getCookieMask();
 
         for (Iterator<OfMockFlowEntry> it = flowEntries.iterator();
              it.hasNext();) {
@@ -800,8 +932,8 @@ public final class OfNode
                 continue;
             }
 
-            if (out != null &&
-                !OfMockUtils.hasOutput(ofent.getInstructions(), out)) {
+            if (outPort != null &&
+                !OfMockUtils.hasOutput(ofent.getInstructions(), outPort)) {
                 continue;
             }
 
@@ -809,13 +941,29 @@ public final class OfNode
                 continue;
             }
 
-            it.remove();
-            removed.add(ofent);
+            if (scanner.flowEntryFound(ofent)) {
+                it.remove();
+            }
         }
+    }
 
+    /**
+     * Process a bulk flow remove request.
+     *
+     * @param input  An input of this RPC.
+     * @return  A list of flow entries removed from the flow table.
+     */
+    private synchronized List<OfMockFlowEntry> removeFlows(
+        RemoveFlowInput input) {
+        FlowEntryRemover remover = new FlowEntryRemover();
+        scanFlowTable(input, remover);
+
+        List<OfMockFlowEntry> removed = remover.getRemovedFlows();
         if (!removed.isEmpty()) {
             notifyAll();
         }
+
+        return removed;
     }
 
     /**
@@ -836,10 +984,10 @@ public final class OfNode
             new org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.mod.removed.MatchBuilder(ofent.getMatch()).build();
 
         SwitchFlowRemovedBuilder builder = new SwitchFlowRemovedBuilder().
-            setPacketCount(BigInteger.valueOf(FRM_PACKET_COUNT)).
-            setByteCount(BigInteger.valueOf(FRM_BYTE_COUNT)).
-            setDurationSec(Long.valueOf(FRM_DURATION_SEC)).
-            setDurationNsec(Long.valueOf(FRM_DURATION_NANOSEC)).
+            setPacketCount(BigInteger.valueOf(PACKET_COUNT)).
+            setByteCount(BigInteger.valueOf(BYTE_COUNT)).
+            setDurationSec(Long.valueOf(DURATION_SEC)).
+            setDurationNsec(Long.valueOf(DURATION_NANOSEC)).
             setPriority(Integer.valueOf(ofent.getPriority())).
             setTableId(Short.valueOf((short)ofent.getTableId())).
             setIdleTimeout(Integer.valueOf(ofent.getIdleTimeout())).
@@ -912,8 +1060,11 @@ public final class OfNode
     @Override
     public Future<RpcResult<RemoveFlowOutput>> removeFlow(
         RemoveFlowInput input) {
-        List<OfMockFlowEntry> removed = new ArrayList<>();
+        List<OfMockFlowEntry> removed;
         if (Boolean.TRUE.equals(input.isStrict())) {
+            removed = new ArrayList<>();
+            FlowCookie cookie = input.getCookie();
+            FlowCookie cookieMask = input.getCookieMask();
             OfMockFlowEntry target;
             try {
                 target = new OfMockFlowEntry(nodeIdentifier, input);
@@ -927,7 +1078,8 @@ public final class OfNode
                 for (Iterator<OfMockFlowEntry> it = flowEntries.iterator();
                      it.hasNext();) {
                     OfMockFlowEntry ofent = it.next();
-                    if (ofent.equals(target)) {
+                    if (ofent.equals(target) &&
+                        checkCookie(ofent.getCookie(), cookie, cookieMask)) {
                         it.remove();
                         removed.add(ofent);
                     }
@@ -938,7 +1090,7 @@ public final class OfNode
                 }
             }
         } else {
-            removeFlows(removed, input);
+            removed = removeFlows(input);
         }
 
         RemovedReasonFlags reason =
@@ -1294,7 +1446,16 @@ public final class OfNode
     @Override
     public Future<RpcResult<GetFlowStatisticsFromFlowTableOutput>> getFlowStatisticsFromFlowTable(
         GetFlowStatisticsFromFlowTableInput input) {
-        return createUnsupported(GetFlowStatisticsFromFlowTableOutput.class);
+        // Start flow statistics read transaction.
+        TransactionId xid = createTransactionId();
+        FlowStatsThread t = new FlowStatsThread(xid, input);
+        t.start();
+
+        // Return the transaction ID only.
+        GetFlowStatisticsFromFlowTableOutputBuilder builder =
+            new GetFlowStatisticsFromFlowTableOutputBuilder();
+        builder.setTransactionId(xid);
+        return createRpcResult(builder.build());
     }
 
     // OpendaylightPortStatisticsService