Bug 5575 pulled out extraction of diff items 26/38326/7
authorAndrej Leitner <anleitne@cisco.com>
Tue, 3 May 2016 12:31:06 +0000 (14:31 +0200)
committerAndrej Leitner <anleitne@cisco.com>
Tue, 24 May 2016 06:32:06 +0000 (08:32 +0200)
  - added common synchronization plan execution strategy
  - added wrapper for diff input
  - removing, renaming & utils

Change-Id: If7876aac23560bb050c1ce2803bf6b732cfba544
Signed-off-by: Andrej Leitner <anleitne@cisco.com>
15 files changed:
applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/SyncPlanPushStrategy.java [new file with mode: 0644]
applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/impl/AbstractFrmSyncListener.java
applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/impl/ForwardingRulesSyncProvider.java
applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/impl/SyncReactorImpl.java
applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/impl/strategy/SyncPlanPushStrategyIncrementalImpl.java [new file with mode: 0644]
applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/impl/strategy/SynchronizationDiffInput.java [new file with mode: 0644]
applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/util/FxChainUtil.java [new file with mode: 0644]
applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/util/ItemSyncBox.java
applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/util/ReconcileUtil.java
applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/util/SyncCrudCounters.java
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/ForwardingRulesSyncProviderTest.java
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/SimplifiedOperationalListenerTest.java
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/SyncReactorImplTest.java
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/strategy/SyncPlanPushStrategyIncrementalImplTest.java [new file with mode: 0644]
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/util/ReconcileUtilTest.java

diff --git a/applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/SyncPlanPushStrategy.java b/applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/SyncPlanPushStrategy.java
new file mode 100644 (file)
index 0000000..9a856cc
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. 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.openflowplugin.applications.frsync;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import org.opendaylight.openflowplugin.applications.frsync.impl.strategy.SynchronizationDiffInput;
+import org.opendaylight.openflowplugin.applications.frsync.util.SyncCrudCounters;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+
+/**
+ * Prescribes common synchronization plan execution strategy.
+ * Implementations should be stateless.
+ */
+public interface SyncPlanPushStrategy {
+
+    /**
+     * @param resultVehicle bootstrap future - execution will chain it's async calls to this one
+     * @param diffInput     wraps all diff data required for any strategy ({add,remove,update} x {flow,group,meter})
+     * @param counters      reference to internal one-shot statistics - summary off successfully pushed items shall be recorded here
+     * @return last future of the chain
+     */
+    ListenableFuture<RpcResult<Void>> executeSyncStrategy(ListenableFuture<RpcResult<Void>> resultVehicle,
+                                                          SynchronizationDiffInput diffInput,
+                                                          SyncCrudCounters counters);
+}
index 32d30361badb1ed0d683b82f60827a4cc30a099d..4d1e53a6932ff16f9fc5671122eb19639aa5a49f 100644 (file)
@@ -8,11 +8,11 @@
 
 package org.opendaylight.openflowplugin.applications.frsync.impl;
 
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.ListenableFuture;
 import java.util.Collection;
 import java.util.concurrent.TimeUnit;
-
 import javax.annotation.Nonnull;
-
 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
@@ -23,9 +23,6 @@ import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Optional;
-import com.google.common.util.concurrent.ListenableFuture;
-
 /**
  * Abstract Listener for node changes.
  */
@@ -56,7 +53,7 @@ public abstract class AbstractFrmSyncListener<T extends DataObject> implements N
             DataTreeModification<T> modification) throws ReadFailedException, InterruptedException;
 
     public abstract LogicalDatastoreType dsType();
-    
+
     static String threadName() {
         final Thread currentThread = Thread.currentThread();
         return currentThread.getName();
index 57d2ac6dfe44d378458d8f7e3bf1c6e8d52a3cd5..0b1b529794d3354baa0cc50c8983e1ff490a85aa 100644 (file)
@@ -18,11 +18,13 @@ import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
 import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
 import org.opendaylight.controller.sal.binding.api.RpcConsumerRegistry;
 import org.opendaylight.openflowplugin.applications.frsync.NodeListener;
+import org.opendaylight.openflowplugin.applications.frsync.SyncPlanPushStrategy;
 import org.opendaylight.openflowplugin.applications.frsync.SyncReactor;
 import org.opendaylight.openflowplugin.applications.frsync.dao.FlowCapableNodeCachedDao;
 import org.opendaylight.openflowplugin.applications.frsync.dao.FlowCapableNodeDao;
 import org.opendaylight.openflowplugin.applications.frsync.dao.FlowCapableNodeOdlDao;
 import org.opendaylight.openflowplugin.applications.frsync.dao.FlowCapableNodeSnapshotDao;
+import org.opendaylight.openflowplugin.applications.frsync.impl.strategy.SyncPlanPushStrategyIncrementalImpl;
 import org.opendaylight.openflowplugin.applications.frsync.util.SemaphoreKeeperGuavaImpl;
 import org.opendaylight.openflowplugin.common.wait.SimpleTaskRetryLooper;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
@@ -121,13 +123,16 @@ public class ForwardingRulesSyncProvider implements AutoCloseable, BindingAwareP
         final TableForwarder tableForwarder = new TableForwarder(salTableService);
 
         {
-            final SyncReactorImpl syncReactorImpl = new SyncReactorImpl();
-            final SyncReactor syncReactorGuard = new SyncReactorGuardDecorator(syncReactorImpl
+            //TODO: make is switchable
+            final SyncPlanPushStrategy syncPlanPushStrategy = new SyncPlanPushStrategyIncrementalImpl()
                     .setFlowForwarder(flowForwarder)
                     .setGroupForwarder(groupForwarder)
                     .setMeterForwarder(meterForwarder)
-                    .setTableForwarder(tableForwarder)
-                    .setTransactionService(transactionService),
+                   .setTableForwarder(tableForwarder)
+                    .setTransactionService(transactionService);
+
+            final SyncReactorImpl syncReactorImpl = new SyncReactorImpl(syncPlanPushStrategy);
+            final SyncReactor syncReactorGuard = new SyncReactorGuardDecorator(syncReactorImpl,
                     new SemaphoreKeeperGuavaImpl<InstanceIdentifier<FlowCapableNode>>(1, true));
 
             final SyncReactor cfgReactor = new SyncReactorFutureWithCompressionDecorator(syncReactorGuard, syncThreadPool);
index bf33a01b61de75b8d6c010809d7ccd527b410f54..b7bc472031ca1d511038d4ce4ab7d052d4dc60e0 100644 (file)
@@ -10,57 +10,32 @@ package org.opendaylight.openflowplugin.applications.frsync.impl;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
-import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.util.concurrent.AsyncFunction;
-import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.JdkFutureAdapters;
 import com.google.common.util.concurrent.ListenableFuture;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
-import javax.annotation.Nullable;
+import org.opendaylight.openflowplugin.applications.frsync.SyncPlanPushStrategy;
 import org.opendaylight.openflowplugin.applications.frsync.SyncReactor;
-import org.opendaylight.openflowplugin.applications.frsync.markandsweep.SwitchFlowId;
+import org.opendaylight.openflowplugin.applications.frsync.impl.strategy.SynchronizationDiffInput;
 import org.opendaylight.openflowplugin.applications.frsync.util.CrudCounts;
 import org.opendaylight.openflowplugin.applications.frsync.util.FlowCapableNodeLookups;
+import org.opendaylight.openflowplugin.applications.frsync.util.FxChainUtil;
 import org.opendaylight.openflowplugin.applications.frsync.util.ItemSyncBox;
 import org.opendaylight.openflowplugin.applications.frsync.util.PathUtil;
 import org.opendaylight.openflowplugin.applications.frsync.util.ReconcileUtil;
 import org.opendaylight.openflowplugin.applications.frsync.util.SyncCrudCounters;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.MeterKey;
 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.service.rev130819.AddFlowOutput;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowOutput;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowOutput;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.FlowCapableTransactionService;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.AddGroupOutput;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.RemoveGroupOutput;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.UpdateGroupOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.Group;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.GroupKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.AddMeterOutput;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.RemoveMeterOutput;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.UpdateMeterOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.MeterId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.table.service.rev131026.UpdateTableOutput;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeatures;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeaturesKey;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
-import org.opendaylight.yangtools.yang.common.RpcError;
 import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
 import org.slf4j.Logger;
@@ -72,12 +47,11 @@ import org.slf4j.LoggerFactory;
 public class SyncReactorImpl implements SyncReactor {
 
     private static final Logger LOG = LoggerFactory.getLogger(SyncReactorImpl.class);
+    private final SyncPlanPushStrategy syncPlanPushStrategy;
 
-    private FlowForwarder flowForwarder;
-    private TableForwarder tableForwarder;
-    private MeterForwarder meterForwarder;
-    private GroupForwarder groupForwarder;
-    private FlowCapableTransactionService transactionService;
+    public SyncReactorImpl(SyncPlanPushStrategy syncPlanPushStrategy) {
+        this.syncPlanPushStrategy = Preconditions.checkNotNull(syncPlanPushStrategy, "execution strategy is mandatory");
+    }
 
     @Override
     public ListenableFuture<Boolean> syncup(final InstanceIdentifier<FlowCapableNode> nodeIdent,
@@ -87,91 +61,37 @@ public class SyncReactorImpl implements SyncReactor {
         final SyncCrudCounters counters = new SyncCrudCounters();
         /**
          * instructions:
-         *  - extract diff changes and preapare change steps in safe order
+         *  - extract diff changes and prepare change steps in safe order
          *    - optimization: decide if updates needed
          *  - execute chosen implementation (e.g. conventional API, bulk API, flat bulk API)
-         *  -
-         * reconciliation strategy - phase 1: - add/update missing objects in following order -
-         * table features - groups (reordered) - meters - flows
+         *  - recommended order follows:
+         * reconciliation strategy - phase 1: - add/update missing objects in following order:
+         *  - table features - groups (reordered) - meters - flows
+         *
+         * reconciliation strategy - phase 2: - remove redundant objects in following order:
+         *  - flows - meters - groups (reordered)
          **/
-        ListenableFuture<RpcResult<Void>> resultVehicle = RpcResultBuilder.<Void>success().buildFuture();
+
         final NodeId nodeId = PathUtil.digNodeId(nodeIdent);
 
-        /* Tables - have to be pushed before groups */
-        // TODO enable table-update when ready
-        //resultVehicle = updateTableFeatures(nodeIdent, configTree);
+        final List<ItemSyncBox<Group>> groupsToAddOrUpdate = extractGroupsToAddOrUpdate(nodeId, configTree, operationalTree);
+        final ItemSyncBox<Meter> metersToAddOrUpdate = extractMetersToAddOrUpdate(nodeId, configTree, operationalTree);
+        final Map<TableKey, ItemSyncBox<Flow>> flowsToAddOrUpdate = extractFlowsToAddOrUpdate(nodeId, configTree, operationalTree);
 
-        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
-            @Override
-            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
-                if (!input.isSuccessful()) {
-                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
-                    //final ListenableFuture<RpcResult<Void>> singleVoidUpdateResult = Futures.transform(
-                    //        Futures.asList Arrays.asList(input, output),
-                    //        ReconcileUtil.<UpdateFlowOutput>createRpcResultCondenser("TODO"));
-                }
-                return addMissingGroups(nodeIdent, configTree, operationalTree, counters);
-            }
-        });
-        Futures.addCallback(resultVehicle, logResultCallback(nodeId, "addMissingGroups"));
-        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
-            @Override
-            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
-                if (!input.isSuccessful()) {
-                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
-                }
-                return addMissingMeters(nodeIdent, configTree, operationalTree, counters);
-            }
-        });
-        Futures.addCallback(resultVehicle, logResultCallback(nodeId, "addMissingMeters"));
-        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
-            @Override
-            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
-                if (!input.isSuccessful()) {
-                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
-                }
-                return addMissingFlows(nodeIdent, configTree, operationalTree, counters);
-            }
-        });
-        Futures.addCallback(resultVehicle, logResultCallback(nodeId, "addMissingFlows"));
+        final Map<TableKey, ItemSyncBox<Flow>> flowsToRemove = extractFlowsToRemove(nodeId, configTree, operationalTree);
+        final ItemSyncBox<Meter> metersToRemove = extractMetersToRemove(nodeId, configTree, operationalTree);
+        final List<ItemSyncBox<Group>> groupsToRemove = extractGroupsToRemove(nodeId, configTree, operationalTree);
 
-        /**
-         * reconciliation strategy - phase 2: - remove redundand objects in following order - flows
-         * - meters - groups (reordered)
-         **/
-        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
-            @Override
-            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
-                if (!input.isSuccessful()) {
-                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
-                }
-                return removeRedundantFlows(nodeIdent, configTree, operationalTree, counters);
-            }
-        });
-        Futures.addCallback(resultVehicle, logResultCallback(nodeId, "removeRedundantFlows"));
-        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
-            @Override
-            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
-                if (!input.isSuccessful()) {
-                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
-                }
-                return removeRedundantMeters(nodeIdent, configTree, operationalTree, counters);
-            }
-        });
-        Futures.addCallback(resultVehicle, logResultCallback(nodeId, "removeRedundantMeters"));
-        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
-            @Override
-            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
-                if (!input.isSuccessful()) {
-                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
-                }
-                return removeRedundantGroups(nodeIdent, configTree, operationalTree, counters);
-            }
-        });
-        Futures.addCallback(resultVehicle, logResultCallback(nodeId, "removeRedundantGroups"));
+        final SynchronizationDiffInput input = new SynchronizationDiffInput(nodeIdent,
+                groupsToAddOrUpdate, metersToAddOrUpdate, flowsToAddOrUpdate,
+                flowsToRemove, metersToRemove, groupsToRemove);
+
+        final ListenableFuture<RpcResult<Void>> bootstrapResultFuture = RpcResultBuilder.<Void>success().buildFuture();
+        final ListenableFuture<RpcResult<Void>> resultVehicle = syncPlanPushStrategy.executeSyncStrategy(
+                bootstrapResultFuture, input, counters);
 
         // log final result
-        Futures.addCallback(resultVehicle, logResultCallback(nodeId, "final result"));
+        Futures.addCallback(resultVehicle, FxChainUtil.logResultCallback(nodeId, "final result"));
 
         return Futures.transform(resultVehicle, new Function<RpcResult<Void>, Boolean>() {
             @Override
@@ -203,596 +123,84 @@ public class SyncReactorImpl implements SyncReactor {
         });
     }
 
-    private FutureCallback<RpcResult<Void>> logResultCallback(final NodeId nodeId, final String prefix) {
-        return new FutureCallback<RpcResult<Void>>() {
-            @Override
-            public void onSuccess(@Nullable final RpcResult<Void> result) {
-                if (result != null) {
-                    if (result.isSuccessful()) {
-                        LOG.debug(prefix + " finished successfully: {}", nodeId.getValue());
-                    } else {
-                        final Collection<RpcError> errors = MoreObjects.firstNonNull(result.getErrors(), ImmutableList.<RpcError>of());
-                        LOG.debug(prefix + " failed: {} -> {}", nodeId.getValue(), Arrays.toString(errors.toArray()));
-                    }
-                } else {
-                    LOG.debug(prefix + "reconciliation failed: {} -> null result", nodeId.getValue());
-                }
-            }
-
-            @Override
-            public void onFailure(final Throwable t) {
-                LOG.debug(prefix + "reconciliation failed seriously: {}", nodeId.getValue(), t);
-            }
-        };
-    }
-
-    public SyncReactorImpl setFlowForwarder(final FlowForwarder flowForwarder) {
-        this.flowForwarder = flowForwarder;
-        return this;
-    }
-
-    public SyncReactorImpl setTableForwarder(final TableForwarder tableForwarder) {
-        this.tableForwarder = tableForwarder;
-        return this;
-    }
-
-    public SyncReactorImpl setMeterForwarder(final MeterForwarder meterForwarder) {
-        this.meterForwarder = meterForwarder;
-        return this;
-    }
-
-    public SyncReactorImpl setGroupForwarder(final GroupForwarder groupForwarder) {
-        this.groupForwarder = groupForwarder;
-        return this;
-    }
-
-    public SyncReactorImpl setTransactionService(final FlowCapableTransactionService transactionService) {
-        this.transactionService = transactionService;
-        return this;
-    }
-
-    ListenableFuture<RpcResult<Void>> updateTableFeatures(final InstanceIdentifier<FlowCapableNode> nodeIdent,
-                                                          final FlowCapableNode flowCapableNodeConfigured) {
-        // CHECK if while pushing the update, updateTableInput can be null to emulate a table add
-        final List<Table> tableList = safeTables(flowCapableNodeConfigured);
-
-        final List<ListenableFuture<RpcResult<UpdateTableOutput>>> allResults = new ArrayList<>();
-        for (Table table : tableList) {
-            TableKey tableKey = table.getKey();
-            KeyedInstanceIdentifier<TableFeatures, TableFeaturesKey> tableFeaturesII = nodeIdent
-                    .child(TableFeatures.class, new TableFeaturesKey(tableKey.getId()));
-            List<TableFeatures> tableFeatures = flowCapableNodeConfigured.getTableFeatures();
-            if (tableFeatures != null) {
-                for (TableFeatures tableFeaturesItem : tableFeatures) {
-                    // TODO uncomment java.lang.NullPointerException
-                    // at
-                    // org.opendaylight.openflowjava.protocol.impl.serialization.match.AbstractOxmMatchEntrySerializer.serializeHeader(AbstractOxmMatchEntrySerializer.java:31
-                    // allResults.add(JdkFutureAdapters.listenInPoolThread(
-                    // tableForwarder.update(tableFeaturesII, null, tableFeaturesItem, nodeIdent)));
-                }
-            }
-        }
-
-        final ListenableFuture<RpcResult<Void>> singleVoidResult = Futures.transform(
-                Futures.allAsList(allResults),
-                ReconcileUtil.<UpdateTableOutput>createRpcResultCondenser("table update"));
-
-        return Futures.transform(singleVoidResult,
-                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
-    }
-
-
     @VisibleForTesting
-    ListenableFuture<RpcResult<Void>> addMissingGroups(final InstanceIdentifier<FlowCapableNode> nodeIdent,
-                                                       final FlowCapableNode flowCapableNodeConfigured,
-                                                       final FlowCapableNode flowCapableNodeOperational,
-                                                       final SyncCrudCounters counters) {
-        final NodeId nodeId = PathUtil.digNodeId(nodeIdent);
-        final List<Group> groupsConfigured = safeGroups(flowCapableNodeConfigured);
-        if (groupsConfigured.isEmpty()) {
-            LOG.trace("no groups configured for node: {} -> SKIPPING", nodeId.getValue());
-            return RpcResultBuilder.<Void>success().buildFuture();
-        }
-
-        final List<Group> groupsOperational = safeGroups(flowCapableNodeOperational);
-
-        return addMissingGroups(nodeId, nodeIdent, groupsConfigured, groupsOperational, counters);
-    }
-
-    protected ListenableFuture<RpcResult<Void>> addMissingGroups(NodeId nodeId,
-                                                                 final InstanceIdentifier<FlowCapableNode> nodeIdent,
-                                                                 final List<Group> groupsConfigured,
-                                                                 final List<Group> groupsOperational,
-                                                                 final SyncCrudCounters counters) {
-
+    static List<ItemSyncBox<Group>> extractGroupsToAddOrUpdate(final NodeId nodeId,
+                                                               final FlowCapableNode flowCapableNodeConfigured,
+                                                               final FlowCapableNode flowCapableNodeOperational) {
+        final List<Group> groupsConfigured = ReconcileUtil.safeGroups(flowCapableNodeConfigured);
+        final List<Group> groupsOperational = ReconcileUtil.safeGroups(flowCapableNodeOperational);
         final Map<Long, Group> groupOperationalMap = FlowCapableNodeLookups.wrapGroupsToMap(groupsOperational);
 
         final List<Group> pendingGroups = new ArrayList<>();
         pendingGroups.addAll(groupsConfigured);
 
-        ListenableFuture<RpcResult<Void>> chainedResult;
-        try {
-            final List<ItemSyncBox<Group>> groupsAddPlan =
-                    ReconcileUtil.resolveAndDivideGroups(nodeId, groupOperationalMap, pendingGroups);
-            if (!groupsAddPlan.isEmpty()) {
-                final CrudCounts groupCrudCounts = counters.getGroupCrudCounts();
-                groupCrudCounts.setAdded(ReconcileUtil.countTotalAdds(groupsAddPlan));
-                groupCrudCounts.setUpdated(ReconcileUtil.countTotalUpdated(groupsAddPlan));
-
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("adding groups: inputGroups={}, planSteps={}, toAddTotal={}, toUpdateTotal={}",
-                            pendingGroups.size(), groupsAddPlan.size(),
-                            groupCrudCounts.getAdded(),
-                            groupCrudCounts.getUpdated());
-                }
-
-                chainedResult = flushAddGroupPortionAndBarrier(nodeIdent, groupsAddPlan.get(0));
-                for (final ItemSyncBox<Group> groupsPortion : Iterables.skip(groupsAddPlan, 1)) {
-                    chainedResult =
-                            Futures.transform(chainedResult, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
-                                @Override
-                                public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input)
-                                        throws Exception {
-                                    final ListenableFuture<RpcResult<Void>> result;
-                                    if (input.isSuccessful()) {
-                                        result = flushAddGroupPortionAndBarrier(nodeIdent, groupsPortion);
-                                    } else {
-                                        // pass through original unsuccessful rpcResult
-                                        result = Futures.immediateFuture(input);
-                                    }
-
-                                    return result;
-                                }
-                            });
-                }
-            } else {
-                chainedResult = RpcResultBuilder.<Void>success().buildFuture();
-            }
-        } catch (IllegalStateException e) {
-            chainedResult = RpcResultBuilder.<Void>failed()
-                    .withError(RpcError.ErrorType.APPLICATION, "failed to add missing groups", e)
-                    .buildFuture();
-        }
-
-        return chainedResult;
-    }
-
-    private ListenableFuture<RpcResult<Void>> flushAddGroupPortionAndBarrier(
-            final InstanceIdentifier<FlowCapableNode> nodeIdent,
-            final ItemSyncBox<Group> groupsPortion) {
-        final List<ListenableFuture<RpcResult<AddGroupOutput>>> allResults = new ArrayList<>();
-        final List<ListenableFuture<RpcResult<UpdateGroupOutput>>> allUpdateResults = new ArrayList<>();
-
-        for (Group group : groupsPortion.getItemsToAdd()) {
-            final KeyedInstanceIdentifier<Group, GroupKey> groupIdent = nodeIdent.child(Group.class, group.getKey());
-            allResults.add(JdkFutureAdapters.listenInPoolThread(groupForwarder.add(groupIdent, group, nodeIdent)));
-
-        }
-
-        for (ItemSyncBox.ItemUpdateTuple<Group> groupTuple : groupsPortion.getItemsToUpdate()) {
-            final Group existingGroup = groupTuple.getOriginal();
-            final Group group = groupTuple.getUpdated();
-
-            final KeyedInstanceIdentifier<Group, GroupKey> groupIdent = nodeIdent.child(Group.class, group.getKey());
-            allUpdateResults.add(JdkFutureAdapters.listenInPoolThread(
-                    groupForwarder.update(groupIdent, existingGroup, group, nodeIdent)));
-        }
-
-        final ListenableFuture<RpcResult<Void>> singleVoidAddResult = Futures.transform(
-                Futures.allAsList(allResults), ReconcileUtil.<AddGroupOutput>createRpcResultCondenser("group add"));
-
-        final ListenableFuture<RpcResult<Void>> singleVoidUpdateResult = Futures.transform(
-                Futures.allAsList(allUpdateResults),
-                ReconcileUtil.<UpdateGroupOutput>createRpcResultCondenser("group update"));
-
-        final ListenableFuture<RpcResult<Void>> summaryResult = Futures.transform(
-                Futures.allAsList(singleVoidAddResult, singleVoidUpdateResult),
-                ReconcileUtil.<Void>createRpcResultCondenser("group add/update"));
-
-
-        return Futures.transform(summaryResult,
-                ReconcileUtil.chainBarrierFlush(
-                        PathUtil.digNodePath(nodeIdent), transactionService));
+        return ReconcileUtil.resolveAndDivideGroupDiffs(nodeId, groupOperationalMap, pendingGroups, true);
     }
 
-    ListenableFuture<RpcResult<Void>> addMissingMeters(final InstanceIdentifier<FlowCapableNode> nodeIdent,
-                                                       final FlowCapableNode flowCapableNodeConfigured,
-                                                       final FlowCapableNode flowCapableNodeOperational,
-                                                       final SyncCrudCounters counters) {
-        final NodeId nodeId = PathUtil.digNodeId(nodeIdent);
-        final List<Meter> metersConfigured = safeMeters(flowCapableNodeConfigured);
-        if (metersConfigured.isEmpty()) {
-            LOG.trace("no meters configured for node: {} -> SKIPPING", nodeId.getValue());
-            return RpcResultBuilder.<Void>success().buildFuture();
-        }
-
-        final List<Meter> metersOperational = safeMeters(flowCapableNodeOperational);
-
-        return addMissingMeters(nodeId, nodeIdent, metersConfigured, metersOperational, counters);
-    }
-
-
-    protected ListenableFuture<RpcResult<Void>> addMissingMeters(NodeId nodeId,
-                                                                 final InstanceIdentifier<FlowCapableNode> nodeIdent,
-                                                                 List<Meter> metersConfigured,
-                                                                 List<Meter> metersOperational,
-                                                                 final SyncCrudCounters counters) {
-
+    @VisibleForTesting
+    static ItemSyncBox<Meter> extractMetersToAddOrUpdate(final NodeId nodeId,
+                                                         final FlowCapableNode flowCapableNodeConfigured,
+                                                         final FlowCapableNode flowCapableNodeOperational) {
+        final List<Meter> metersConfigured = ReconcileUtil.safeMeters(flowCapableNodeConfigured);
+        final List<Meter> metersOperational = ReconcileUtil.safeMeters(flowCapableNodeOperational);
         final Map<MeterId, Meter> meterOperationalMap = FlowCapableNodeLookups.wrapMetersToMap(metersOperational);
-        final CrudCounts meterCrudCounts = counters.getMeterCrudCounts();
-
-        final List<ListenableFuture<RpcResult<AddMeterOutput>>> allResults = new ArrayList<>();
-        final List<ListenableFuture<RpcResult<UpdateMeterOutput>>> allUpdateResults = new ArrayList<>();
-        for (Meter meter : metersConfigured) {
-            final Meter existingMeter = meterOperationalMap.get(meter.getMeterId());
-            final KeyedInstanceIdentifier<Meter, MeterKey> meterIdent = nodeIdent.child(Meter.class, meter.getKey());
-
-            if (existingMeter == null) {
-                LOG.debug("adding meter {} - absent on device {}",
-                        meter.getMeterId(), nodeId);
-                allResults.add(JdkFutureAdapters.listenInPoolThread(
-                        meterForwarder.add(meterIdent, meter, nodeIdent)));
-                meterCrudCounts.incAdded();
-            } else {
-                // compare content and eventually update
-                LOG.trace("meter {} - already present on device {} .. comparing", meter.getMeterId(), nodeId);
-                if (!meter.equals(existingMeter)) {
-                    LOG.trace("meter {} - needs update on device {}", meter.getMeterId(), nodeId);
-                    allUpdateResults.add(JdkFutureAdapters.listenInPoolThread(
-                            meterForwarder.update(meterIdent, existingMeter, meter, nodeIdent)));
-                    meterCrudCounts.incUpdated();
-                } else {
-                    LOG.trace("meter {} - on device {} is equal to the configured one", meter.getMeterId(), nodeId);
-                }
-            }
-        }
-
-        final ListenableFuture<RpcResult<Void>> singleVoidAddResult = Futures.transform(
-                Futures.allAsList(allResults), ReconcileUtil.<AddMeterOutput>createRpcResultCondenser("meter add"));
-
-        final ListenableFuture<RpcResult<Void>> singleVoidUpdateResult = Futures.transform(
-                Futures.allAsList(allUpdateResults),
-                ReconcileUtil.<UpdateMeterOutput>createRpcResultCondenser("meter update"));
 
-        final ListenableFuture<RpcResult<Void>> summaryResults = Futures.transform(
-                Futures.allAsList(singleVoidUpdateResult, singleVoidAddResult),
-                ReconcileUtil.<Void>createRpcResultCondenser("meter add/update"));
-
-        return summaryResults;
-
-        /*
-        return Futures.transform(summaryResults,
-                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
-                */
+        return ReconcileUtil.resolveMeterDiffs(nodeId, meterOperationalMap, metersConfigured, true);
     }
 
     @VisibleForTesting
-    ListenableFuture<RpcResult<Void>> addMissingFlows(final InstanceIdentifier<FlowCapableNode> nodeIdent,
-                                                      final FlowCapableNode flowCapableNodeConfigured,
-                                                      final FlowCapableNode flowCapableNodeOperational,
-                                                      final SyncCrudCounters counters) {
-        final NodeId nodeId = PathUtil.digNodeId(nodeIdent);
-        final List<Table> tablesConfigured = safeTables(flowCapableNodeConfigured);
+    static Map<TableKey, ItemSyncBox<Flow>> extractFlowsToAddOrUpdate(final NodeId nodeId,
+                                                                      final FlowCapableNode flowCapableNodeConfigured,
+                                                                      final FlowCapableNode flowCapableNodeOperational) {
+        final List<Table> tablesConfigured = ReconcileUtil.safeTables(flowCapableNodeConfigured);
         if (tablesConfigured.isEmpty()) {
-            LOG.trace("no tables in config for node: {} -> SKIPPING", nodeId.getValue());
-            return RpcResultBuilder.<Void>success().buildFuture();
+            return Collections.emptyMap();
         }
 
-        final List<Table> tablesOperational = safeTables(flowCapableNodeOperational);
-
-        return addMissingFlows(nodeId, nodeIdent, tablesConfigured, tablesOperational, counters);
-    }
-
-    protected ListenableFuture<RpcResult<Void>> addMissingFlows(NodeId nodeId,
-                                                                final InstanceIdentifier<FlowCapableNode> nodeIdent,
-                                                                List<Table> tablesConfigured, List<Table> tablesOperational,
-                                                                final SyncCrudCounters counters) {
-
+        final List<Table> tablesOperational = ReconcileUtil.safeTables(flowCapableNodeOperational);
         final Map<Short, Table> tableOperationalMap = FlowCapableNodeLookups.wrapTablesToMap(tablesOperational);
-        final List<ListenableFuture<RpcResult<AddFlowOutput>>> allResults = new ArrayList<>();
-        final List<ListenableFuture<RpcResult<UpdateFlowOutput>>> allUpdateResults = new ArrayList<>();
-        final CrudCounts flowCrudCounts = counters.getFlowCrudCounts();
-
-        for (final Table tableConfigured : tablesConfigured) {
-            final List<Flow> flowsConfigured = tableConfigured.getFlow();
-            if (flowsConfigured == null || flowsConfigured.isEmpty()) {
-                continue;
-            }
-
-            final KeyedInstanceIdentifier<Table, TableKey> tableIdent =
-                    nodeIdent.child(Table.class, tableConfigured.getKey());
-
-            // lookup table (on device)
-            final Table tableOperational = tableOperationalMap.get(tableConfigured.getId());
-            // wrap existing (on device) flows in current table into map
-            final Map<SwitchFlowId, Flow> flowOperationalMap = FlowCapableNodeLookups.wrapFlowsToMap(
-                    tableOperational != null
-                            ? tableOperational.getFlow()
-                            : null);
-
-
-            // loop configured flows and check if already present on device
-            for (final Flow flow : flowsConfigured) {
-                final Flow existingFlow = FlowCapableNodeLookups.flowMapLookupExisting(flow, flowOperationalMap);
-                final KeyedInstanceIdentifier<Flow, FlowKey> flowIdent = tableIdent.child(Flow.class, flow.getKey());
-
-                if (existingFlow == null) {
-                    LOG.debug("adding flow {} in table {} - absent on device {} match{}",
-                            flow.getId(), tableConfigured.getKey(), nodeId, flow.getMatch());
-
-                    allResults.add(JdkFutureAdapters.listenInPoolThread(
-                            flowForwarder.add(flowIdent, flow, nodeIdent)));
-                    flowCrudCounts.incAdded();
-                } else {
-                    LOG.trace("flow {} in table {} - already present on device {} .. comparing match{}",
-                            flow.getId(), tableConfigured.getKey(), nodeId, flow.getMatch());
-                    // check instructions and eventually update
-                    if (!Objects.equals(flow.getInstructions(), existingFlow.getInstructions())) {
-                        LOG.trace("flow {} in table {} - needs update on device {} match{}",
-                                flow.getId(), tableConfigured.getKey(), nodeId, flow.getMatch());
-                        allUpdateResults.add(JdkFutureAdapters.listenInPoolThread(
-                                flowForwarder.update(flowIdent, existingFlow, flow, nodeIdent)));
-                        flowCrudCounts.incUpdated();
-                    } else {
-                        LOG.trace("flow {} in table {} - is equal to configured one on device {} match{}",
-                                flow.getId(), tableConfigured.getKey(), nodeId, flow.getMatch());
-                    }
-                }
-            }
-        }
-
-        final ListenableFuture<RpcResult<Void>> singleVoidAddResult = Futures.transform(
-                Futures.allAsList(allResults),
-                ReconcileUtil.<AddFlowOutput>createRpcResultCondenser("flow adding"));
-
-        final ListenableFuture<RpcResult<Void>> singleVoidUpdateResult = Futures.transform(
-                Futures.allAsList(allUpdateResults),
-                ReconcileUtil.<UpdateFlowOutput>createRpcResultCondenser("flow updating"));
-
-        final ListenableFuture<RpcResult<Void>> summaryResult = Futures.transform(
-                Futures.allAsList(singleVoidAddResult, singleVoidUpdateResult),
-                ReconcileUtil.<Void>createRpcResultCondenser("flow add/update"));
 
-        return summaryResult;
-
-        /*
-        return Futures.transform(summaryResult,
-                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
-                */
+        return ReconcileUtil.resolveFlowDiffsInAllTables(nodeId, tableOperationalMap, tablesConfigured, true);
     }
 
     @VisibleForTesting
-    ListenableFuture<RpcResult<Void>> removeRedundantFlows(
-            final InstanceIdentifier<FlowCapableNode> nodeIdent,
-            final FlowCapableNode flowCapableNodeConfigured,
-            final FlowCapableNode flowCapableNodeOperational,
-            final SyncCrudCounters counters) {
-        final NodeId nodeId = PathUtil.digNodeId(nodeIdent);
-        final List<Table> tablesOperational = safeTables(flowCapableNodeOperational);
-
+    static Map<TableKey, ItemSyncBox<Flow>> extractFlowsToRemove(final NodeId nodeId,
+                                                                 final FlowCapableNode flowCapableNodeConfigured,
+                                                                 final FlowCapableNode flowCapableNodeOperational) {
+        final List<Table> tablesOperational = ReconcileUtil.safeTables(flowCapableNodeOperational);
         if (tablesOperational.isEmpty()) {
-            LOG.trace("no tables in operational for node: {} -> SKIPPING", nodeId.getValue());
-            return RpcResultBuilder.<Void>success().buildFuture();
-        }
-
-        final List<Table> tablesConfigured = safeTables(flowCapableNodeConfigured);
-
-        return removeRedundantFlows(nodeId, nodeIdent, tablesConfigured, tablesOperational, counters);
-    }
-
-    protected ListenableFuture<RpcResult<Void>> removeRedundantFlows(
-            NodeId nodeId, final InstanceIdentifier<FlowCapableNode> nodeIdent,
-            final List<Table> tablesConfigured, final List<Table> tablesOperational, final SyncCrudCounters counters) {
-        final Map<Short, Table> tableConfigMap = FlowCapableNodeLookups.wrapTablesToMap(tablesConfigured);
-        final List<ListenableFuture<RpcResult<RemoveFlowOutput>>> allResults = new ArrayList<>();
-        final CrudCounts flowCrudCounts = counters.getFlowCrudCounts();
-
-        for (final Table tableOperational : tablesOperational) {
-            final List<Flow> flowsOperational = tableOperational.getFlow();
-            if (flowsOperational == null || flowsOperational.isEmpty()) {
-                continue;
-            }
-
-            final KeyedInstanceIdentifier<Table, TableKey> tableIdent =
-                    nodeIdent.child(Table.class, tableOperational.getKey());
-
-            // lookup configured table
-            final Table tableConfig = tableConfigMap.get(tableOperational.getId());
-            // wrap configured flows in current table into map
-            final Map<SwitchFlowId, Flow> flowConfigMap = FlowCapableNodeLookups.wrapFlowsToMap(
-                    tableConfig != null
-                            ? tableConfig.getFlow()
-                            : null);
-
-            // loop flows on device and check if the are configured
-            for (final Flow flow : flowsOperational) {
-                final Flow existingFlow = FlowCapableNodeLookups.flowMapLookupExisting(flow, flowConfigMap);
-                if (existingFlow == null) {
-                    LOG.trace("removing flow {} in table {} - absent in config {}, match {}",
-                            flow.getId(), tableOperational.getKey(), nodeId, flow.getMatch());
-
-                    final KeyedInstanceIdentifier<Flow, FlowKey> flowIdent =
-                            tableIdent.child(Flow.class, flow.getKey());
-                    allResults.add(JdkFutureAdapters.listenInPoolThread(
-                            flowForwarder.remove(flowIdent, flow, nodeIdent)));
-                    flowCrudCounts.incRemoved();
-                } else {
-                    LOG.trace("skipping flow {} in table {} - present in config {}, match {}",
-                            flow.getId(), tableOperational.getKey(), nodeId, flow.getMatch());
-                }
-            }
+            return Collections.emptyMap();
         }
 
-        final ListenableFuture<RpcResult<Void>> singleVoidResult = Futures.transform(
-                Futures.allAsList(allResults), ReconcileUtil.<RemoveFlowOutput>createRpcResultCondenser("flow remove"));
-        return Futures.transform(singleVoidResult,
-                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
+        final List<Table> tablesConfigured = ReconcileUtil.safeTables(flowCapableNodeConfigured);
+        final Map<Short, Table> tableConfiguredMap = FlowCapableNodeLookups.wrapTablesToMap(tablesConfigured);
 
+        return ReconcileUtil.resolveFlowDiffsInAllTables(nodeId, tableConfiguredMap, tablesOperational, false);
     }
 
     @VisibleForTesting
-    ListenableFuture<RpcResult<Void>> removeRedundantMeters(final InstanceIdentifier<FlowCapableNode> nodeIdent,
-                                                            final FlowCapableNode flowCapableNodeConfigured,
-                                                            final FlowCapableNode flowCapableNodeOperational,
-                                                            final SyncCrudCounters counters) {
-
-        final NodeId nodeId = PathUtil.digNodeId(nodeIdent);
-        final List<Meter> metersOperational = safeMeters(flowCapableNodeOperational);
-        if (metersOperational.isEmpty()) {
-            LOG.trace("no meters on device for node: {} -> SKIPPING", nodeId.getValue());
-            return RpcResultBuilder.<Void>success().buildFuture();
-        }
-
-        final List<Meter> metersConfigured = safeMeters(flowCapableNodeConfigured);
+    static ItemSyncBox<Meter> extractMetersToRemove(final NodeId nodeId,
+                                                    final FlowCapableNode flowCapableNodeConfigured,
+                                                    final FlowCapableNode flowCapableNodeOperational) {
+        final List<Meter> metersConfigured = ReconcileUtil.safeMeters(flowCapableNodeConfigured);
+        final List<Meter> metersOperational = ReconcileUtil.safeMeters(flowCapableNodeOperational);
+        final Map<MeterId, Meter> meterConfiguredMap = FlowCapableNodeLookups.wrapMetersToMap(metersConfigured);
 
-        return removeRedundantMeters(nodeId, nodeIdent, metersConfigured, metersOperational, counters);
-    }
-
-
-    protected ListenableFuture<RpcResult<Void>> removeRedundantMeters(NodeId nodeId,
-                                                                      final InstanceIdentifier<FlowCapableNode> nodeIdent,
-                                                                      List<Meter> metersConfigured,
-                                                                      List<Meter> metersOperational,
-                                                                      final SyncCrudCounters counters) {
-
-        final Map<MeterId, Meter> meterConfigMap = FlowCapableNodeLookups.wrapMetersToMap(metersConfigured);
-        final CrudCounts meterCrudCounts = counters.getMeterCrudCounts();
-
-        final List<ListenableFuture<RpcResult<RemoveMeterOutput>>> allResults = new ArrayList<>();
-        for (Meter meter : metersOperational) {
-            if (!meterConfigMap.containsKey(meter.getMeterId())) {
-                LOG.trace("removing meter {} - absent in config {}",
-                        meter.getMeterId(), nodeId);
-                final KeyedInstanceIdentifier<Meter, MeterKey> meterIdent =
-                        nodeIdent.child(Meter.class, meter.getKey());
-                allResults.add(JdkFutureAdapters.listenInPoolThread(
-                        meterForwarder.remove(meterIdent, meter, nodeIdent)));
-                meterCrudCounts.incRemoved();
-            } else {
-                LOG.trace("skipping meter {} - present in config {}",
-                        meter.getMeterId(), nodeId);
-            }
-        }
-
-        final ListenableFuture<RpcResult<Void>> singleVoidResult = Futures.transform(
-                Futures.allAsList(allResults),
-                ReconcileUtil.<RemoveMeterOutput>createRpcResultCondenser("meter remove"));
-        return singleVoidResult;
-        /*
-        return Futures.transform(singleVoidResult,
-                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
-                */
+        return ReconcileUtil.resolveMeterDiffs(nodeId, meterConfiguredMap, metersOperational, false);
     }
 
     @VisibleForTesting
-    ListenableFuture<RpcResult<Void>> removeRedundantGroups(final InstanceIdentifier<FlowCapableNode> nodeIdent,
-                                                            final FlowCapableNode flowCapableNodeConfigured,
-                                                            final FlowCapableNode flowCapableNodeOperational,
-                                                            final SyncCrudCounters counters) {
-        final NodeId nodeId = PathUtil.digNodeId(nodeIdent);
-        final List<Group> groupsOperational = safeGroups(flowCapableNodeOperational);
-        if (groupsOperational == null || groupsOperational.isEmpty()) {
-            LOG.trace("no groups on device for node: {} -> SKIPPING", nodeId.getValue());
-            return RpcResultBuilder.<Void>success().buildFuture();
-        }
-
-        final List<Group> groupsConfigured = safeGroups(flowCapableNodeConfigured);
-
-        return removeRedundantGroups(nodeId, nodeIdent, groupsConfigured, groupsOperational, counters);
-    }
-
-    ListenableFuture<RpcResult<Void>> removeRedundantGroups(NodeId nodeId,
-                                                            final InstanceIdentifier<FlowCapableNode> nodeIdent,
-                                                            List<Group> groupsConfigured, List<Group> groupsOperational,
-                                                            final SyncCrudCounters counters) {
-
-        final Map<Long, Group> groupConfigMap = FlowCapableNodeLookups.wrapGroupsToMap(groupsConfigured);
-        final CrudCounts groupCrudCounts = counters.getGroupCrudCounts();
+    static List<ItemSyncBox<Group>> extractGroupsToRemove(final NodeId nodeId,
+                                                          final FlowCapableNode flowCapableNodeConfigured,
+                                                          final FlowCapableNode flowCapableNodeOperational) {
+        final List<Group> groupsConfigured = ReconcileUtil.safeGroups(flowCapableNodeConfigured);
+        final List<Group> groupsOperational = ReconcileUtil.safeGroups(flowCapableNodeOperational);
+        final Map<Long, Group> groupConfiguredMap = FlowCapableNodeLookups.wrapGroupsToMap(groupsConfigured);
 
         final List<Group> pendingGroups = new ArrayList<>();
         pendingGroups.addAll(groupsOperational);
 
-        ListenableFuture<RpcResult<Void>> chainedResult;
-        try {
-            final List<ItemSyncBox<Group>> groupsRemovePlan =
-                    ReconcileUtil.resolveAndDivideGroups(nodeId, groupConfigMap, pendingGroups, false);
-            if (!groupsRemovePlan.isEmpty()) {
-                groupCrudCounts.setRemoved(ReconcileUtil.countTotalAdds(groupsRemovePlan));
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("removing groups: inputGroups={}, planSteps={}, toRemoveTotal={}",
-                            pendingGroups.size(), groupsRemovePlan.size(),
-                            groupCrudCounts.getRemoved());
-                }
-                Collections.reverse(groupsRemovePlan);
-                chainedResult = flushRemoveGroupPortionAndBarrier(nodeIdent, groupsRemovePlan.get(0));
-                for (final ItemSyncBox<Group> groupsPortion : Iterables.skip(groupsRemovePlan, 1)) {
-                    chainedResult =
-                            Futures.transform(chainedResult, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
-                                @Override
-                                public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input)
-                                        throws Exception {
-                                    final ListenableFuture<RpcResult<Void>> result;
-                                    if (input.isSuccessful()) {
-                                        result = flushRemoveGroupPortionAndBarrier(nodeIdent, groupsPortion);
-                                    } else {
-                                        // pass through original unsuccessful rpcResult
-                                        result = Futures.immediateFuture(input);
-                                    }
-
-                                    return result;
-                                }
-                            });
-                }
-            } else {
-                chainedResult = RpcResultBuilder.<Void>success().buildFuture();
-            }
-        } catch (IllegalStateException e) {
-            chainedResult = RpcResultBuilder.<Void>failed()
-                    .withError(RpcError.ErrorType.APPLICATION, "failed to add missing groups", e)
-                    .buildFuture();
-        }
-
-        return chainedResult;
-    }
-
-    static List<Group> safeGroups(FlowCapableNode node) {
-        if (node == null) {
-            return Collections.emptyList();
-        }
-
-        return MoreObjects.firstNonNull(node.getGroup(), ImmutableList.<Group>of());
-    }
-
-    static List<Table> safeTables(FlowCapableNode node) {
-        if (node == null) {
-            return Collections.emptyList();
-        }
-
-        return MoreObjects.firstNonNull(node.getTable(), ImmutableList.<Table>of());
-    }
-
-    static List<Meter> safeMeters(FlowCapableNode node) {
-        if (node == null) {
-            return Collections.emptyList();
-        }
-
-        return MoreObjects.firstNonNull(node.getMeter(), ImmutableList.<Meter>of());
+        return ReconcileUtil.resolveAndDivideGroupDiffs(nodeId, groupConfiguredMap, pendingGroups, false);
     }
 
-    private ListenableFuture<RpcResult<Void>> flushRemoveGroupPortionAndBarrier(
-            final InstanceIdentifier<FlowCapableNode> nodeIdent,
-            final ItemSyncBox<Group> groupsPortion) {
-        List<ListenableFuture<RpcResult<RemoveGroupOutput>>> allResults = new ArrayList<>();
-        for (Group group : groupsPortion.getItemsToAdd()) {
-            final KeyedInstanceIdentifier<Group, GroupKey> groupIdent = nodeIdent.child(Group.class, group.getKey());
-            allResults.add(JdkFutureAdapters.listenInPoolThread(groupForwarder.remove(groupIdent, group, nodeIdent)));
-        }
-
-        final ListenableFuture<RpcResult<Void>> singleVoidResult = Futures.transform(
-                Futures.allAsList(allResults),
-                ReconcileUtil.<RemoveGroupOutput>createRpcResultCondenser("group remove"));
-
-        return Futures.transform(singleVoidResult,
-                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
-    }
 }
diff --git a/applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/impl/strategy/SyncPlanPushStrategyIncrementalImpl.java b/applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/impl/strategy/SyncPlanPushStrategyIncrementalImpl.java
new file mode 100644 (file)
index 0000000..d79c5fd
--- /dev/null
@@ -0,0 +1,546 @@
+/**
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. 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.openflowplugin.applications.frsync.impl.strategy;
+
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.JdkFutureAdapters;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.opendaylight.openflowplugin.applications.frsync.SyncPlanPushStrategy;
+import org.opendaylight.openflowplugin.applications.frsync.impl.FlowForwarder;
+import org.opendaylight.openflowplugin.applications.frsync.impl.GroupForwarder;
+import org.opendaylight.openflowplugin.applications.frsync.impl.MeterForwarder;
+import org.opendaylight.openflowplugin.applications.frsync.impl.TableForwarder;
+import org.opendaylight.openflowplugin.applications.frsync.util.CrudCounts;
+import org.opendaylight.openflowplugin.applications.frsync.util.FxChainUtil;
+import org.opendaylight.openflowplugin.applications.frsync.util.ItemSyncBox;
+import org.opendaylight.openflowplugin.applications.frsync.util.PathUtil;
+import org.opendaylight.openflowplugin.applications.frsync.util.ReconcileUtil;
+import org.opendaylight.openflowplugin.applications.frsync.util.SyncCrudCounters;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.MeterKey;
+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.service.rev130819.AddFlowOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.FlowCapableTransactionService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.AddGroupOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.RemoveGroupOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.UpdateGroupOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.Group;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.GroupKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.AddMeterOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.RemoveMeterOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.UpdateMeterOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.service.rev131026.UpdateTableOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeatures;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeaturesKey;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Execute CRUD API for flow + group + meter involving one-by-one (incremental) strategy.
+ */
+public class SyncPlanPushStrategyIncrementalImpl implements SyncPlanPushStrategy {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SyncPlanPushStrategyIncrementalImpl.class);
+
+    private FlowForwarder flowForwarder;
+    private TableForwarder tableForwarder;
+    private MeterForwarder meterForwarder;
+    private GroupForwarder groupForwarder;
+    private FlowCapableTransactionService transactionService;
+
+    @Override
+    public ListenableFuture<RpcResult<Void>> executeSyncStrategy(ListenableFuture<RpcResult<Void>> resultVehicle,
+                                                                 final SynchronizationDiffInput diffInput,
+                                                                 final SyncCrudCounters counters) {
+        final InstanceIdentifier<FlowCapableNode> nodeIdent = diffInput.getNodeIdent();
+        final NodeId nodeId = PathUtil.digNodeId(nodeIdent);
+
+        /* Tables - have to be pushed before groups */
+        // TODO enable table-update when ready
+        //resultVehicle = updateTableFeatures(nodeIdent, configTree);
+
+        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
+            @Override
+            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
+                if (!input.isSuccessful()) {
+                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
+                    //final ListenableFuture<RpcResult<Void>> singleVoidUpdateResult = Futures.transform(
+                    //        Futures.asList Arrays.asList(input, output),
+                    //        ReconcileUtil.<UpdateFlowOutput>createRpcResultCondenser("TODO"));
+                }
+                return addMissingGroups(nodeId, nodeIdent, diffInput.getGroupsToAddOrUpdate(), counters);
+            }
+        });
+        Futures.addCallback(resultVehicle, FxChainUtil.logResultCallback(nodeId, "addMissingGroups"));
+        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
+            @Override
+            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
+                if (!input.isSuccessful()) {
+                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
+                }
+                return addMissingMeters(nodeId, nodeIdent, diffInput.getMetersToAddOrUpdate(), counters);
+            }
+        });
+        Futures.addCallback(resultVehicle, FxChainUtil.logResultCallback(nodeId, "addMissingMeters"));
+        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
+            @Override
+            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
+                if (!input.isSuccessful()) {
+                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
+                }
+                return addMissingFlows(nodeId, nodeIdent, diffInput.getFlowsToAddOrUpdate(), counters);
+            }
+        });
+        Futures.addCallback(resultVehicle, FxChainUtil.logResultCallback(nodeId, "addMissingFlows"));
+
+
+        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
+            @Override
+            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
+                if (!input.isSuccessful()) {
+                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
+                }
+                return removeRedundantFlows(nodeId, nodeIdent, diffInput.getFlowsToRemove(), counters);
+            }
+        });
+        Futures.addCallback(resultVehicle, FxChainUtil.logResultCallback(nodeId, "removeRedundantFlows"));
+        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
+            @Override
+            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
+                if (!input.isSuccessful()) {
+                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
+                }
+                return removeRedundantMeters(nodeId, nodeIdent, diffInput.getMetersToRemove(), counters);
+            }
+        });
+        Futures.addCallback(resultVehicle, FxChainUtil.logResultCallback(nodeId, "removeRedundantMeters"));
+        resultVehicle = Futures.transform(resultVehicle, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
+            @Override
+            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input) throws Exception {
+                if (!input.isSuccessful()) {
+                    //TODO chain errors but not skip processing on first error return Futures.immediateFuture(input);
+                }
+                return removeRedundantGroups(nodeId, nodeIdent, diffInput.getGroupsToRemove(), counters);
+            }
+        });
+        Futures.addCallback(resultVehicle, FxChainUtil.logResultCallback(nodeId, "removeRedundantGroups"));
+        return resultVehicle;
+    }
+
+
+    protected ListenableFuture<RpcResult<Void>> addMissingFlows(final NodeId nodeId,
+                                                                final InstanceIdentifier<FlowCapableNode> nodeIdent,
+                                                                final Map<TableKey, ItemSyncBox<Flow>> flowsInTablesSyncBox,
+                                                                final SyncCrudCounters counters) {
+        if (flowsInTablesSyncBox.isEmpty()) {
+            LOG.trace("no tables in config for node: {} -> SKIPPING", nodeId.getValue());
+            return RpcResultBuilder.<Void>success().buildFuture();
+        }
+
+        final List<ListenableFuture<RpcResult<AddFlowOutput>>> allResults = new ArrayList<>();
+        final List<ListenableFuture<RpcResult<UpdateFlowOutput>>> allUpdateResults = new ArrayList<>();
+        final CrudCounts flowCrudCounts = counters.getFlowCrudCounts();
+
+        for (Map.Entry<TableKey, ItemSyncBox<Flow>> flowsInTableBoxEntry : flowsInTablesSyncBox.entrySet()) {
+            final TableKey tableKey = flowsInTableBoxEntry.getKey();
+            final ItemSyncBox<Flow> flowSyncBox = flowsInTableBoxEntry.getValue();
+
+            final KeyedInstanceIdentifier<Table, TableKey> tableIdent = nodeIdent.child(Table.class, tableKey);
+
+            for (final Flow flow : flowSyncBox.getItemsToPush()) {
+                final KeyedInstanceIdentifier<Flow, FlowKey> flowIdent = tableIdent.child(Flow.class, flow.getKey());
+
+                LOG.trace("adding flow {} in table {} - absent on device {} match{}",
+                        flow.getId(), tableKey, nodeId, flow.getMatch());
+
+                allResults.add(JdkFutureAdapters.listenInPoolThread(
+                        flowForwarder.add(flowIdent, flow, nodeIdent)));
+                flowCrudCounts.incAdded();
+            }
+
+            for (final ItemSyncBox.ItemUpdateTuple<Flow> flowUpdate : flowSyncBox.getItemsToUpdate()) {
+                final Flow existingFlow = flowUpdate.getOriginal();
+                final Flow updatedFlow = flowUpdate.getUpdated();
+
+                final KeyedInstanceIdentifier<Flow, FlowKey> flowIdent = tableIdent.child(Flow.class, updatedFlow.getKey());
+                LOG.trace("flow {} in table {} - needs update on device {} match{}",
+                        updatedFlow.getId(), tableKey, nodeId, updatedFlow.getMatch());
+
+                allUpdateResults.add(JdkFutureAdapters.listenInPoolThread(
+                        flowForwarder.update(flowIdent, existingFlow, updatedFlow, nodeIdent)));
+                flowCrudCounts.incUpdated();
+            }
+        }
+
+        final ListenableFuture<RpcResult<Void>> singleVoidAddResult = Futures.transform(
+                Futures.allAsList(allResults),
+                ReconcileUtil.<AddFlowOutput>createRpcResultCondenser("flow adding"));
+
+        final ListenableFuture<RpcResult<Void>> singleVoidUpdateResult = Futures.transform(
+                Futures.allAsList(allUpdateResults),
+                ReconcileUtil.<UpdateFlowOutput>createRpcResultCondenser("flow updating"));
+
+        final ListenableFuture<RpcResult<Void>> summaryResult = Futures.transform(
+                Futures.allAsList(singleVoidAddResult, singleVoidUpdateResult),
+                ReconcileUtil.<Void>createRpcResultCondenser("flow add/update"));
+
+        return summaryResult;
+
+        /*
+        return Futures.transform(summaryResult,
+                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
+                */
+    }
+
+    protected ListenableFuture<RpcResult<Void>> removeRedundantFlows(final NodeId nodeId,
+                                                                     final InstanceIdentifier<FlowCapableNode> nodeIdent,
+                                                                     final Map<TableKey, ItemSyncBox<Flow>> removalPlan,
+                                                                     final SyncCrudCounters counters) {
+        if (removalPlan.isEmpty()) {
+            LOG.trace("no tables in operational for node: {} -> SKIPPING", nodeId.getValue());
+            return RpcResultBuilder.<Void>success().buildFuture();
+        }
+
+        final List<ListenableFuture<RpcResult<RemoveFlowOutput>>> allResults = new ArrayList<>();
+        final CrudCounts flowCrudCounts = counters.getFlowCrudCounts();
+
+        for (final Map.Entry<TableKey, ItemSyncBox<Flow>> flowsPerTable : removalPlan.entrySet()) {
+            final KeyedInstanceIdentifier<Table, TableKey> tableIdent =
+                    nodeIdent.child(Table.class, flowsPerTable.getKey());
+
+            // loop flows on device and check if the are configured
+            for (final Flow flow : flowsPerTable.getValue().getItemsToPush()) {
+                final KeyedInstanceIdentifier<Flow, FlowKey> flowIdent =
+                        tableIdent.child(Flow.class, flow.getKey());
+                allResults.add(JdkFutureAdapters.listenInPoolThread(
+                        flowForwarder.remove(flowIdent, flow, nodeIdent)));
+                flowCrudCounts.incRemoved();
+            }
+        }
+
+        final ListenableFuture<RpcResult<Void>> singleVoidResult = Futures.transform(
+                Futures.allAsList(allResults), ReconcileUtil.<RemoveFlowOutput>createRpcResultCondenser("flow remove"));
+        return Futures.transform(singleVoidResult,
+                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
+
+    }
+
+    protected ListenableFuture<RpcResult<Void>> removeRedundantMeters(final NodeId nodeId,
+                                                                      final InstanceIdentifier<FlowCapableNode> nodeIdent,
+                                                                      final ItemSyncBox<Meter> meterRemovalPlan,
+                                                                      final SyncCrudCounters counters) {
+        if (meterRemovalPlan.isEmpty()) {
+            LOG.trace("no meters on device for node: {} -> SKIPPING", nodeId.getValue());
+            return RpcResultBuilder.<Void>success().buildFuture();
+        }
+
+        final CrudCounts meterCrudCounts = counters.getMeterCrudCounts();
+
+        final List<ListenableFuture<RpcResult<RemoveMeterOutput>>> allResults = new ArrayList<>();
+        for (Meter meter : meterRemovalPlan.getItemsToPush()) {
+            LOG.trace("removing meter {} - absent in config {}",
+                    meter.getMeterId(), nodeId);
+            final KeyedInstanceIdentifier<Meter, MeterKey> meterIdent =
+                    nodeIdent.child(Meter.class, meter.getKey());
+            allResults.add(JdkFutureAdapters.listenInPoolThread(
+                    meterForwarder.remove(meterIdent, meter, nodeIdent)));
+            meterCrudCounts.incRemoved();
+        }
+
+        final ListenableFuture<RpcResult<Void>> singleVoidResult = Futures.transform(
+                Futures.allAsList(allResults),
+                ReconcileUtil.<RemoveMeterOutput>createRpcResultCondenser("meter remove"));
+        return singleVoidResult;
+        /*
+        return Futures.transform(singleVoidResult,
+                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
+                */
+    }
+
+    ListenableFuture<RpcResult<Void>> removeRedundantGroups(final NodeId nodeId,
+                                                            final InstanceIdentifier<FlowCapableNode> nodeIdent,
+                                                            final List<ItemSyncBox<Group>> groupsRemovalPlan,
+                                                            final SyncCrudCounters counters) {
+        if (groupsRemovalPlan.isEmpty()) {
+            LOG.trace("no groups on device for node: {} -> SKIPPING", nodeId.getValue());
+            return RpcResultBuilder.<Void>success().buildFuture();
+        }
+
+        final CrudCounts groupCrudCounts = counters.getGroupCrudCounts();
+
+        ListenableFuture<RpcResult<Void>> chainedResult = RpcResultBuilder.<Void>success().buildFuture();
+        try {
+            groupCrudCounts.setRemoved(ReconcileUtil.countTotalAdds(groupsRemovalPlan));
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("removing groups: planSteps={}, toRemoveTotal={}",
+                        groupsRemovalPlan.size(), groupCrudCounts.getRemoved());
+            }
+            Collections.reverse(groupsRemovalPlan);
+            for (final ItemSyncBox<Group> groupsPortion : groupsRemovalPlan) {
+                chainedResult =
+                        Futures.transform(chainedResult, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
+                            @Override
+                            public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input)
+                                    throws Exception {
+                                final ListenableFuture<RpcResult<Void>> result;
+                                if (input.isSuccessful()) {
+                                    result = flushRemoveGroupPortionAndBarrier(nodeIdent, groupsPortion);
+                                } else {
+                                    // pass through original unsuccessful rpcResult
+                                    result = Futures.immediateFuture(input);
+                                }
+
+                                return result;
+                            }
+                        });
+            }
+        } catch (IllegalStateException e) {
+            chainedResult = RpcResultBuilder.<Void>failed()
+                    .withError(RpcError.ErrorType.APPLICATION, "failed to add missing groups", e)
+                    .buildFuture();
+        }
+
+        return chainedResult;
+    }
+
+    private ListenableFuture<RpcResult<Void>> flushRemoveGroupPortionAndBarrier(
+            final InstanceIdentifier<FlowCapableNode> nodeIdent,
+            final ItemSyncBox<Group> groupsPortion) {
+        List<ListenableFuture<RpcResult<RemoveGroupOutput>>> allResults = new ArrayList<>();
+        for (Group group : groupsPortion.getItemsToPush()) {
+            final KeyedInstanceIdentifier<Group, GroupKey> groupIdent = nodeIdent.child(Group.class, group.getKey());
+            allResults.add(JdkFutureAdapters.listenInPoolThread(groupForwarder.remove(groupIdent, group, nodeIdent)));
+        }
+
+        final ListenableFuture<RpcResult<Void>> singleVoidResult = Futures.transform(
+                Futures.allAsList(allResults),
+                ReconcileUtil.<RemoveGroupOutput>createRpcResultCondenser("group remove"));
+
+        return Futures.transform(singleVoidResult,
+                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
+    }
+
+    ListenableFuture<RpcResult<Void>> updateTableFeatures(final InstanceIdentifier<FlowCapableNode> nodeIdent,
+                                                          final FlowCapableNode flowCapableNodeConfigured) {
+        // CHECK if while pushing the update, updateTableInput can be null to emulate a table add
+        final List<Table> tableList = ReconcileUtil.safeTables(flowCapableNodeConfigured);
+
+        final List<ListenableFuture<RpcResult<UpdateTableOutput>>> allResults = new ArrayList<>();
+        for (Table table : tableList) {
+            TableKey tableKey = table.getKey();
+            KeyedInstanceIdentifier<TableFeatures, TableFeaturesKey> tableFeaturesII = nodeIdent
+                    .child(TableFeatures.class, new TableFeaturesKey(tableKey.getId()));
+            List<TableFeatures> tableFeatures = flowCapableNodeConfigured.getTableFeatures();
+            if (tableFeatures != null) {
+                for (TableFeatures tableFeaturesItem : tableFeatures) {
+                    // TODO uncomment java.lang.NullPointerException
+                    // at
+                    // org.opendaylight.openflowjava.protocol.impl.serialization.match.AbstractOxmMatchEntrySerializer.serializeHeader(AbstractOxmMatchEntrySerializer.java:31
+                    // allResults.add(JdkFutureAdapters.listenInPoolThread(
+                    // tableForwarder.update(tableFeaturesII, null, tableFeaturesItem, nodeIdent)));
+                }
+            }
+        }
+
+        final ListenableFuture<RpcResult<Void>> singleVoidResult = Futures.transform(
+                Futures.allAsList(allResults),
+                ReconcileUtil.<UpdateTableOutput>createRpcResultCondenser("table update"));
+
+        return Futures.transform(singleVoidResult,
+                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
+    }
+
+    private ListenableFuture<RpcResult<Void>> flushAddGroupPortionAndBarrier(
+            final InstanceIdentifier<FlowCapableNode> nodeIdent,
+            final ItemSyncBox<Group> groupsPortion) {
+        final List<ListenableFuture<RpcResult<AddGroupOutput>>> allResults = new ArrayList<>();
+        final List<ListenableFuture<RpcResult<UpdateGroupOutput>>> allUpdateResults = new ArrayList<>();
+
+        for (Group group : groupsPortion.getItemsToPush()) {
+            final KeyedInstanceIdentifier<Group, GroupKey> groupIdent = nodeIdent.child(Group.class, group.getKey());
+            allResults.add(JdkFutureAdapters.listenInPoolThread(groupForwarder.add(groupIdent, group, nodeIdent)));
+
+        }
+
+        for (ItemSyncBox.ItemUpdateTuple<Group> groupTuple : groupsPortion.getItemsToUpdate()) {
+            final Group existingGroup = groupTuple.getOriginal();
+            final Group group = groupTuple.getUpdated();
+
+            final KeyedInstanceIdentifier<Group, GroupKey> groupIdent = nodeIdent.child(Group.class, group.getKey());
+            allUpdateResults.add(JdkFutureAdapters.listenInPoolThread(
+                    groupForwarder.update(groupIdent, existingGroup, group, nodeIdent)));
+        }
+
+        final ListenableFuture<RpcResult<Void>> singleVoidAddResult = Futures.transform(
+                Futures.allAsList(allResults), ReconcileUtil.<AddGroupOutput>createRpcResultCondenser("group add"));
+
+        final ListenableFuture<RpcResult<Void>> singleVoidUpdateResult = Futures.transform(
+                Futures.allAsList(allUpdateResults),
+                ReconcileUtil.<UpdateGroupOutput>createRpcResultCondenser("group update"));
+
+        final ListenableFuture<RpcResult<Void>> summaryResult = Futures.transform(
+                Futures.allAsList(singleVoidAddResult, singleVoidUpdateResult),
+                ReconcileUtil.<Void>createRpcResultCondenser("group add/update"));
+
+
+        return Futures.transform(summaryResult,
+                ReconcileUtil.chainBarrierFlush(
+                        PathUtil.digNodePath(nodeIdent), transactionService));
+    }
+
+    protected ListenableFuture<RpcResult<Void>> addMissingMeters(final NodeId nodeId,
+                                                                 final InstanceIdentifier<FlowCapableNode> nodeIdent,
+                                                                 final ItemSyncBox<Meter> syncBox,
+                                                                 final SyncCrudCounters counters) {
+        if (syncBox.isEmpty()) {
+            LOG.trace("no meters configured for node: {} -> SKIPPING", nodeId.getValue());
+            return RpcResultBuilder.<Void>success().buildFuture();
+        }
+
+        final CrudCounts meterCrudCounts = counters.getMeterCrudCounts();
+
+        final List<ListenableFuture<RpcResult<AddMeterOutput>>> allResults = new ArrayList<>();
+        final List<ListenableFuture<RpcResult<UpdateMeterOutput>>> allUpdateResults = new ArrayList<>();
+        for (Meter meter : syncBox.getItemsToPush()) {
+            final KeyedInstanceIdentifier<Meter, MeterKey> meterIdent = nodeIdent.child(Meter.class, meter.getKey());
+            LOG.debug("adding meter {} - absent on device {}",
+                    meter.getMeterId(), nodeId);
+            allResults.add(JdkFutureAdapters.listenInPoolThread(
+                    meterForwarder.add(meterIdent, meter, nodeIdent)));
+            meterCrudCounts.incAdded();
+        }
+
+        for (ItemSyncBox.ItemUpdateTuple<Meter> meterTuple : syncBox.getItemsToUpdate()) {
+            final Meter existingMeter = meterTuple.getOriginal();
+            final Meter updated = meterTuple.getUpdated();
+            final KeyedInstanceIdentifier<Meter, MeterKey> meterIdent = nodeIdent.child(Meter.class, updated.getKey());
+            LOG.trace("meter {} - needs update on device {}", updated.getMeterId(), nodeId);
+            allUpdateResults.add(JdkFutureAdapters.listenInPoolThread(
+                    meterForwarder.update(meterIdent, existingMeter, updated, nodeIdent)));
+            meterCrudCounts.incUpdated();
+        }
+
+        final ListenableFuture<RpcResult<Void>> singleVoidAddResult = Futures.transform(
+                Futures.allAsList(allResults), ReconcileUtil.<AddMeterOutput>createRpcResultCondenser("meter add"));
+
+        final ListenableFuture<RpcResult<Void>> singleVoidUpdateResult = Futures.transform(
+                Futures.allAsList(allUpdateResults),
+                ReconcileUtil.<UpdateMeterOutput>createRpcResultCondenser("meter update"));
+
+        final ListenableFuture<RpcResult<Void>> summaryResults = Futures.transform(
+                Futures.allAsList(singleVoidUpdateResult, singleVoidAddResult),
+                ReconcileUtil.<Void>createRpcResultCondenser("meter add/update"));
+
+        return summaryResults;
+
+        /*
+        return Futures.transform(summaryResults,
+                ReconcileUtil.chainBarrierFlush(PathUtil.digNodePath(nodeIdent), transactionService));
+                */
+    }
+
+    protected ListenableFuture<RpcResult<Void>> addMissingGroups(final NodeId nodeId,
+                                                                 final InstanceIdentifier<FlowCapableNode> nodeIdent,
+                                                                 final List<ItemSyncBox<Group>> groupsAddPlan,
+                                                                 final SyncCrudCounters counters) {
+        if (groupsAddPlan.isEmpty()) {
+            LOG.trace("no groups configured for node: {} -> SKIPPING", nodeId.getValue());
+            return RpcResultBuilder.<Void>success().buildFuture();
+        }
+
+        ListenableFuture<RpcResult<Void>> chainedResult;
+        try {
+            if (!groupsAddPlan.isEmpty()) {
+                final CrudCounts groupCrudCounts = counters.getGroupCrudCounts();
+                groupCrudCounts.setAdded(ReconcileUtil.countTotalAdds(groupsAddPlan));
+                groupCrudCounts.setUpdated(ReconcileUtil.countTotalUpdated(groupsAddPlan));
+
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("adding groups: planSteps={}, toAddTotal={}, toUpdateTotal={}",
+                            groupsAddPlan.size(),
+                            groupCrudCounts.getAdded(),
+                            groupCrudCounts.getUpdated());
+                }
+
+                chainedResult = flushAddGroupPortionAndBarrier(nodeIdent, groupsAddPlan.get(0));
+                for (final ItemSyncBox<Group> groupsPortion : Iterables.skip(groupsAddPlan, 1)) {
+                    chainedResult =
+                            Futures.transform(chainedResult, new AsyncFunction<RpcResult<Void>, RpcResult<Void>>() {
+                                @Override
+                                public ListenableFuture<RpcResult<Void>> apply(final RpcResult<Void> input)
+                                        throws Exception {
+                                    final ListenableFuture<RpcResult<Void>> result;
+                                    if (input.isSuccessful()) {
+                                        result = flushAddGroupPortionAndBarrier(nodeIdent, groupsPortion);
+                                    } else {
+                                        // pass through original unsuccessful rpcResult
+                                        result = Futures.immediateFuture(input);
+                                    }
+
+                                    return result;
+                                }
+                            });
+                }
+            } else {
+                chainedResult = RpcResultBuilder.<Void>success().buildFuture();
+            }
+        } catch (IllegalStateException e) {
+            chainedResult = RpcResultBuilder.<Void>failed()
+                    .withError(RpcError.ErrorType.APPLICATION, "failed to add missing groups", e)
+                    .buildFuture();
+        }
+
+        return chainedResult;
+    }
+
+
+    public SyncPlanPushStrategyIncrementalImpl setFlowForwarder(final FlowForwarder flowForwarder) {
+        this.flowForwarder = flowForwarder;
+        return this;
+    }
+
+    public SyncPlanPushStrategyIncrementalImpl setTableForwarder(final TableForwarder tableForwarder) {
+        this.tableForwarder = tableForwarder;
+        return this;
+    }
+
+    public SyncPlanPushStrategyIncrementalImpl setMeterForwarder(final MeterForwarder meterForwarder) {
+        this.meterForwarder = meterForwarder;
+        return this;
+    }
+
+    public SyncPlanPushStrategyIncrementalImpl setGroupForwarder(final GroupForwarder groupForwarder) {
+        this.groupForwarder = groupForwarder;
+        return this;
+    }
+
+    public SyncPlanPushStrategyIncrementalImpl setTransactionService(final FlowCapableTransactionService transactionService) {
+        this.transactionService = transactionService;
+        return this;
+    }
+
+}
diff --git a/applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/impl/strategy/SynchronizationDiffInput.java b/applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/impl/strategy/SynchronizationDiffInput.java
new file mode 100644 (file)
index 0000000..a0b9fde
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. 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.openflowplugin.applications.frsync.impl.strategy;
+
+import java.util.List;
+import java.util.Map;
+import org.opendaylight.openflowplugin.applications.frsync.util.ItemSyncBox;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter;
+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.group.types.rev131018.groups.Group;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Wraps all the required inputs (diffs) for synchronization strategy execution.
+ */
+public class SynchronizationDiffInput {
+
+    private final InstanceIdentifier<FlowCapableNode> nodeIdent;
+    final List<ItemSyncBox<Group>> groupsToAddOrUpdate;
+    final ItemSyncBox<Meter> metersToAddOrUpdate;
+    final Map<TableKey, ItemSyncBox<Flow>> flowsToAddOrUpdate;
+    final Map<TableKey, ItemSyncBox<Flow>> flowsToRemove;
+    final ItemSyncBox<Meter> metersToRemove;
+    final List<ItemSyncBox<Group>> groupsToRemove;
+
+    public SynchronizationDiffInput(final InstanceIdentifier<FlowCapableNode> nodeIdent,
+                                    final List<ItemSyncBox<Group>> groupsToAddOrUpdate,
+                                    final ItemSyncBox<Meter> metersToAddOrUpdate,
+                                    final Map<TableKey, ItemSyncBox<Flow>> flowsToAddOrUpdate,
+                                    final Map<TableKey, ItemSyncBox<Flow>> flowsToRemove,
+                                    final ItemSyncBox<Meter> metersToRemove,
+                                    final List<ItemSyncBox<Group>> groupsToRemove) {
+        this.nodeIdent = nodeIdent;
+        this.groupsToAddOrUpdate = groupsToAddOrUpdate;
+        this.metersToAddOrUpdate = metersToAddOrUpdate;
+        this.flowsToAddOrUpdate = flowsToAddOrUpdate;
+        this.flowsToRemove = flowsToRemove;
+        this.metersToRemove = metersToRemove;
+        this.groupsToRemove = groupsToRemove;
+    }
+
+    public InstanceIdentifier<FlowCapableNode> getNodeIdent() {
+        return nodeIdent;
+    }
+
+    public List<ItemSyncBox<Group>> getGroupsToAddOrUpdate() {
+        return groupsToAddOrUpdate;
+    }
+
+    public ItemSyncBox<Meter> getMetersToAddOrUpdate() {
+        return metersToAddOrUpdate;
+    }
+
+    public Map<TableKey, ItemSyncBox<Flow>> getFlowsToAddOrUpdate() {
+        return flowsToAddOrUpdate;
+    }
+
+    public Map<TableKey, ItemSyncBox<Flow>> getFlowsToRemove() {
+        return flowsToRemove;
+    }
+
+    public ItemSyncBox<Meter> getMetersToRemove() {
+        return metersToRemove;
+    }
+
+    public List<ItemSyncBox<Group>> getGroupsToRemove() {
+        return groupsToRemove;
+    }
+}
diff --git a/applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/util/FxChainUtil.java b/applications/forwardingrules-sync/src/main/java/org/opendaylight/openflowplugin/applications/frsync/util/FxChainUtil.java
new file mode 100644 (file)
index 0000000..7eddebb
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. 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.openflowplugin.applications.frsync.util;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.FutureCallback;
+import java.util.Arrays;
+import java.util.Collection;
+import javax.annotation.Nullable;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Util methods for {@link com.google.common.util.concurrent.ListenableFuture} chaining.
+ */
+public class FxChainUtil {
+
+    private static final Logger LOG = LoggerFactory.getLogger(FxChainUtil.class);
+
+
+    public static FutureCallback<RpcResult<Void>> logResultCallback(final NodeId nodeId, final String prefix) {
+        return new FutureCallback<RpcResult<Void>>() {
+            @Override
+            public void onSuccess(@Nullable final RpcResult<Void> result) {
+                if (result != null) {
+                    if (result.isSuccessful()) {
+                        LOG.debug(prefix + " finished successfully: {}", nodeId.getValue());
+                    } else {
+                        final Collection<RpcError> errors = MoreObjects.firstNonNull(result.getErrors(), ImmutableList.<RpcError>of());
+                        LOG.debug(prefix + " failed: {} -> {}", nodeId.getValue(), Arrays.toString(errors.toArray()));
+                    }
+                } else {
+                    LOG.debug(prefix + "reconciliation failed: {} -> null result", nodeId.getValue());
+                }
+            }
+
+            @Override
+            public void onFailure(final Throwable t) {
+                LOG.debug(prefix + "reconciliation failed seriously: {}", nodeId.getValue(), t);
+            }
+        };
+    }
+}
index d83dfabd472668eda9db47896f6d58432fb01a50..6635d0a5d3ddbc4e44c479366fcbdbc69cb51420 100644 (file)
@@ -8,7 +8,7 @@
 
 package org.opendaylight.openflowplugin.applications.frsync.util;
 
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Set;
 
 /**
@@ -17,11 +17,11 @@ import java.util.Set;
  */
 public class ItemSyncBox<I> {
 
-    private Set<I> itemsToAdd = new HashSet<>();
-    private Set<ItemUpdateTuple<I>> itemsToUpdate = new HashSet<>();
+    private Set<I> itemsToPush = new LinkedHashSet<>();
+    private Set<ItemUpdateTuple<I>> itemsToUpdate = new LinkedHashSet<>();
 
-    public Set<I> getItemsToAdd() {
-        return itemsToAdd;
+    public Set<I> getItemsToPush() {
+        return itemsToPush;
     }
 
     public Set<ItemUpdateTuple<I>> getItemsToUpdate() {
@@ -29,7 +29,7 @@ public class ItemSyncBox<I> {
     }
 
     public boolean isEmpty() {
-        return itemsToAdd.isEmpty() && itemsToUpdate.isEmpty();
+        return itemsToPush.isEmpty() && itemsToUpdate.isEmpty();
     }
 
     /**
index b041b49c05c0c530bcd6e648295d5e23a9240c8a..913606ccabd7fa81d5e5a1b2457b39ee4a71ed81 100644 (file)
@@ -8,18 +8,23 @@
 
 package org.opendaylight.openflowplugin.applications.frsync.util;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.JdkFutureAdapters;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.Collections;
+import org.opendaylight.openflowplugin.applications.frsync.markandsweep.SwitchFlowId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.GroupActionCase;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter;
+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.transaction.rev150304.FlowCapableTransactionService;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.SendBarrierInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.SendBarrierInputBuilder;
@@ -28,6 +33,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.MeterId;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.common.RpcError;
 import org.opendaylight.yangtools.yang.common.RpcResult;
@@ -35,11 +41,15 @@ import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Iterables;
-import com.google.common.util.concurrent.AsyncFunction;
-import com.google.common.util.concurrent.JdkFutureAdapters;
-import com.google.common.util.concurrent.ListenableFuture;
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 
 /**
  * Util methods for group reconcil task (future chaining, transforms).
@@ -101,16 +111,29 @@ public class ReconcileUtil {
         };
     }
 
-    public static List<ItemSyncBox<Group>> resolveAndDivideGroups(final NodeId nodeId,
-                                                                  final Map<Long, Group> installedGroupsArg,
-                                                                  final Collection<Group> pendingGroups) {
-        return resolveAndDivideGroups(nodeId, installedGroupsArg, pendingGroups, true);
+    /**
+     * @param nodeId             target node
+     * @param installedGroupsArg groups resent on device
+     * @param pendingGroups      groups configured for device
+     * @return list of safe synchronization steps with updates
+     */
+    public static List<ItemSyncBox<Group>> resolveAndDivideGroupDiffs(final NodeId nodeId,
+                                                                      final Map<Long, Group> installedGroupsArg,
+                                                                      final Collection<Group> pendingGroups) {
+        return resolveAndDivideGroupDiffs(nodeId, installedGroupsArg, pendingGroups, true);
     }
 
-    public static List<ItemSyncBox<Group>> resolveAndDivideGroups(final NodeId nodeId,
-                                                                  final Map<Long, Group> installedGroupsArg,
-                                                                  final Collection<Group> pendingGroups,
-                                                                  final boolean gatherUpdates) {
+    /**
+     * @param nodeId             target node
+     * @param installedGroupsArg groups resent on device
+     * @param pendingGroups      groups configured for device
+     * @param gatherUpdates      check content of pending item if present on device (and create update task eventually)
+     * @return list of safe synchronization steps
+     */
+    public static List<ItemSyncBox<Group>> resolveAndDivideGroupDiffs(final NodeId nodeId,
+                                                                      final Map<Long, Group> installedGroupsArg,
+                                                                      final Collection<Group> pendingGroups,
+                                                                      final boolean gatherUpdates) {
 
         final Map<Long, Group> installedGroups = new HashMap<>(installedGroupsArg);
         final List<ItemSyncBox<Group>> plan = new ArrayList<>();
@@ -142,7 +165,7 @@ public class ReconcileUtil {
                 } else if (checkGroupPrecondition(installedGroups.keySet(), group)) {
                     iterator.remove();
                     installIncrement.put(group.getGroupId().getValue(), group);
-                    stepPlan.getItemsToAdd().add(group);
+                    stepPlan.getItemsToPush().add(group);
                 }
             }
 
@@ -186,7 +209,7 @@ public class ReconcileUtil {
     public static <E> int countTotalAdds(final List<ItemSyncBox<E>> groupsAddPlan) {
         int count = 0;
         for (ItemSyncBox<E> groupItemSyncBox : groupsAddPlan) {
-            count += groupItemSyncBox.getItemsToAdd().size();
+            count += groupItemSyncBox.getItemsToPush().size();
         }
         return count;
     }
@@ -198,4 +221,119 @@ public class ReconcileUtil {
         }
         return count;
     }
+
+    /**
+     * @param nodeId              target node
+     * @param meterOperationalMap meters present on device
+     * @param metersConfigured    meters configured for device
+     * @param gatherUpdates       check content of pending item if present on device (and create update task eventually)
+     * @return synchronization box
+     */
+    public static ItemSyncBox<Meter> resolveMeterDiffs(final NodeId nodeId,
+                                                       final Map<MeterId, Meter> meterOperationalMap,
+                                                       final List<Meter> metersConfigured,
+                                                       final boolean gatherUpdates) {
+        LOG.trace("resolving meters for {}", nodeId);
+        final ItemSyncBox<Meter> syncBox = new ItemSyncBox<>();
+        for (Meter meter : metersConfigured) {
+            final Meter existingMeter = meterOperationalMap.get(meter.getMeterId());
+            if (existingMeter == null) {
+                syncBox.getItemsToPush().add(meter);
+            } else {
+                // compare content and eventually update
+                if (gatherUpdates && !meter.equals(existingMeter)) {
+                    syncBox.getItemsToUpdate().add(new ItemSyncBox.ItemUpdateTuple<>(existingMeter, meter));
+                }
+            }
+        }
+        return syncBox;
+    }
+
+    /**
+     * @param flowsConfigured    flows resent on device
+     * @param flowOperationalMap flows configured for device
+     * @param gatherUpdates      check content of pending item if present on device (and create update task eventually)
+     * @return list of safe synchronization steps
+     */
+    @VisibleForTesting
+    static ItemSyncBox<Flow> resolveFlowDiffsInTable(final List<Flow> flowsConfigured,
+                                                     final Map<SwitchFlowId, Flow> flowOperationalMap,
+                                                     final boolean gatherUpdates) {
+        final ItemSyncBox<Flow> flowsSyncBox = new ItemSyncBox<>();
+        // loop configured flows and check if already present on device
+        for (final Flow flow : flowsConfigured) {
+            final Flow existingFlow = FlowCapableNodeLookups.flowMapLookupExisting(flow, flowOperationalMap);
+
+            if (existingFlow == null) {
+                flowsSyncBox.getItemsToPush().add(flow);
+            } else {
+                // check instructions and eventually update
+                if (gatherUpdates && !Objects.equals(flow.getInstructions(), existingFlow.getInstructions())) {
+                    flowsSyncBox.getItemsToUpdate().add(new ItemSyncBox.ItemUpdateTuple<>(existingFlow, flow));
+                }
+            }
+        }
+        return flowsSyncBox;
+    }
+
+    /**
+     * @param nodeId              target node
+     * @param tableOperationalMap flow-tables resent on device
+     * @param tablesConfigured    flow-tables configured for device
+     * @param gatherUpdates       check content of pending item if present on device (and create update task eventually)
+     * @return map : key={@link TableKey}, value={@link ItemSyncBox} of safe synchronization steps
+     */
+    public static Map<TableKey, ItemSyncBox<Flow>> resolveFlowDiffsInAllTables(final NodeId nodeId,
+                                                                               final Map<Short, Table> tableOperationalMap,
+                                                                               final List<Table> tablesConfigured,
+                                                                               final boolean gatherUpdates) {
+        LOG.trace("resolving flows in tables for {}", nodeId);
+        final Map<TableKey, ItemSyncBox<Flow>> tableFlowSyncBoxes = new HashMap<>();
+        for (final Table tableConfigured : tablesConfigured) {
+            final List<Flow> flowsConfigured = tableConfigured.getFlow();
+            if (flowsConfigured == null || flowsConfigured.isEmpty()) {
+                continue;
+            }
+
+            // lookup table (on device)
+            final Table tableOperational = tableOperationalMap.get(tableConfigured.getId());
+            // wrap existing (on device) flows in current table into map
+            final Map<SwitchFlowId, Flow> flowOperationalMap = FlowCapableNodeLookups.wrapFlowsToMap(
+                    tableOperational != null
+                            ? tableOperational.getFlow()
+                            : null);
+
+
+            final ItemSyncBox<Flow> flowsSyncBox = resolveFlowDiffsInTable(
+                    flowsConfigured, flowOperationalMap, gatherUpdates);
+            if (!flowsSyncBox.isEmpty()) {
+                tableFlowSyncBoxes.put(tableConfigured.getKey(), flowsSyncBox);
+            }
+        }
+        return tableFlowSyncBoxes;
+    }
+
+    public static List<Group> safeGroups(FlowCapableNode node) {
+        if (node == null) {
+            return Collections.emptyList();
+        }
+
+        return MoreObjects.firstNonNull(node.getGroup(), ImmutableList.<Group>of());
+    }
+
+    public static List<Table> safeTables(FlowCapableNode node) {
+        if (node == null) {
+            return Collections.emptyList();
+        }
+
+        return MoreObjects.firstNonNull(node.getTable(), ImmutableList.<Table>of());
+    }
+
+    public static List<Meter> safeMeters(FlowCapableNode node) {
+        if (node == null) {
+            return Collections.emptyList();
+        }
+
+        return MoreObjects.firstNonNull(node.getMeter(), ImmutableList.<Meter>of());
+    }
 }
index 34a670c1b6651b0b03ff619c60ebc623a669bcfd..a832d2cc340c67703b88167794fb474d278b0b49 100644 (file)
@@ -5,7 +5,6 @@
  * 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.openflowplugin.applications.frsync.util;
 
 /**
index b99463311b9dc939bf4f00a17725e343ff812c4b..1b1fac25e5f91068fe6239d87c122f0303d6efd2 100644 (file)
@@ -5,7 +5,6 @@
  * 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.openflowplugin.applications.frsync.impl;
 
 import org.junit.Before;
index c4683fad502d67740768cf9916996e7eb87e75a3..4be870f5e8bd48dc6225ac7639b523b803787146 100644 (file)
@@ -48,6 +48,7 @@ import com.google.common.util.concurrent.Futures;
 @RunWith(MockitoJUnitRunner.class)
 public class SimplifiedOperationalListenerTest {
 
+    public static final NodeId NODE_ID = new NodeId("testNode");
     @Mock
     private SyncReactor reactor;
     @Mock
@@ -59,7 +60,6 @@ public class SimplifiedOperationalListenerTest {
     @Mock
     private DataObjectModification<Node> operationalModification;
 
-    private NodeId nodeId;
     private InstanceIdentifier<Node> nodePath;
     private InstanceIdentifier<FlowCapableNode> fcNodePath;
     private SimplifiedOperationalListener nodeListenerOperational;
@@ -72,10 +72,10 @@ public class SimplifiedOperationalListenerTest {
         final FlowCapableNodeDao configDao = new FlowCapableNodeCachedDao(configSnaphot,
                 new FlowCapableNodeOdlDao(db, LogicalDatastoreType.CONFIGURATION));
 
-        nodeId = new NodeId("testNode");
+
         nodeListenerOperational = new SimplifiedOperationalListener(reactor, operationalSnaphot, configDao);
         nodePath = InstanceIdentifier.create(Nodes.class)
-                .child(Node.class, new NodeKey(nodeId));
+                .child(Node.class, new NodeKey(NODE_ID));
         fcNodePath = nodePath.augmentation(FlowCapableNode.class);
     }
 
@@ -91,8 +91,8 @@ public class SimplifiedOperationalListenerTest {
         final FlowCapableNode mockOperationalFlowCapableNode = Mockito.mock(FlowCapableNode.class);
         Mockito.when(mockOperationalNode.getAugmentation(FlowCapableNode.class))
             .thenReturn(mockOperationalFlowCapableNode);
-        Mockito.when(mockOperationalNode.getId()).thenReturn(nodeId);
-        
+        Mockito.when(mockOperationalNode.getId()).thenReturn(NODE_ID);
+
         final DataTreeIdentifier<Node> dataTreeIdentifier =
                 new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL, nodePath);
 
index 94e3723c747c35ded9bc12e269d30af4b34a8896..0567cdc5122dabd77b88ed9507bcdb5a22860ed9 100644 (file)
@@ -8,84 +8,25 @@
 
 package org.opendaylight.openflowplugin.applications.frsync.impl;
 
-import com.google.common.util.concurrent.ListenableFuture;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
-import org.mockito.InOrder;
-import org.mockito.Matchers;
 import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
 import org.mockito.runners.MockitoJUnitRunner;
-import org.mockito.stubbing.Answer;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
-import org.opendaylight.openflowplugin.applications.frsync.util.SyncCrudCounters;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.GroupActionCase;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.GroupActionCaseBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.OutputActionCaseBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.group.action._case.GroupAction;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.group.action._case.GroupActionBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.output.action._case.OutputActionBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder;
+import org.opendaylight.openflowplugin.applications.frsync.SyncPlanPushStrategy;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.MeterBuilder;
-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.TableBuilder;
 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.FlowBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutputBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowOutputBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowOutputBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.FlowCapableTransactionService;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.SendBarrierInput;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.AddGroupOutputBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.RemoveGroupOutputBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.UpdateGroupOutputBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.GroupId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group.Buckets;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group.BucketsBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group.buckets.Bucket;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group.buckets.BucketBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.Group;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.GroupBuilder;
 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.meter.service.rev130918.AddMeterOutputBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.RemoveMeterOutputBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.UpdateMeterOutputBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.BandId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.MeterId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.band.type.band.type.DropBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.meter.MeterBandHeadersBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.meter.meter.band.headers.MeterBandHeaderBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.table.service.rev131026.UpdateTableOutputBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeatures;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeaturesBuilder;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.common.RpcResult;
-import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -105,15 +46,7 @@ public class SyncReactorImplTest {
     @Mock
     private DataBroker db;
     @Mock
-    private GroupForwarder groupCommitter;
-    @Mock
-    private FlowForwarder flowCommitter;
-    @Mock
-    private MeterForwarder meterCommitter;
-    @Mock
-    private TableForwarder tableCommitter;
-    @Mock
-    private FlowCapableTransactionService flowCapableTxService;
+    private SyncPlanPushStrategy syncPlanPushStrategy;
 
     @Captor
     private ArgumentCaptor<Group> groupCaptor;
@@ -130,675 +63,13 @@ public class SyncReactorImplTest {
     @Captor
     private ArgumentCaptor<TableFeatures> tableFeaturesCaptor;
 
-    private SyncCrudCounters counters;
-
     @Before
     public void setUp() throws Exception {
-        Mockito.when(flowCapableTxService.sendBarrier(Matchers.<SendBarrierInput>any()))
-                .thenReturn(RpcResultBuilder.success((Void) null).buildFuture());
-
-        Mockito.doAnswer(createSalServiceFutureAnswer()).when(groupCommitter).add(
-                Matchers.<InstanceIdentifier<Group>>any(), Matchers.<Group>any(),
-                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
-        Mockito.doAnswer(createSalServiceFutureAnswer()).when(groupCommitter).update(
-                Matchers.<InstanceIdentifier<Group>>any(), Matchers.<Group>any(), Matchers.<Group>any(),
-                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
-        Mockito.doAnswer(createSalServiceFutureAnswer()).when(groupCommitter).remove(
-                Matchers.<InstanceIdentifier<Group>>any(), Matchers.<Group>any(),
-                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
-
-        Mockito.doAnswer(createSalServiceFutureAnswer()).when(flowCommitter).add(
-                Matchers.<InstanceIdentifier<Flow>>any(), Matchers.<Flow>any(),
-                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
-        Mockito.doAnswer(createSalServiceFutureAnswer()).when(flowCommitter).update(
-                Matchers.<InstanceIdentifier<Flow>>any(), Matchers.<Flow>any(), Matchers.<Flow>any(),
-                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
-        Mockito.doAnswer(createSalServiceFutureAnswer()).when(flowCommitter).remove(
-                Matchers.<InstanceIdentifier<Flow>>any(), Matchers.<Flow>any(),
-                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
-
-        Mockito.doAnswer(createSalServiceFutureAnswer()).when(meterCommitter).add(
-                Matchers.<InstanceIdentifier<Meter>>any(), Matchers.<Meter>any(),
-                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
-        Mockito.doAnswer(createSalServiceFutureAnswer()).when(meterCommitter).update(
-                Matchers.<InstanceIdentifier<Meter>>any(), Matchers.<Meter>any(), Matchers.<Meter>any(),
-                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
-        Mockito.doAnswer(createSalServiceFutureAnswer()).when(meterCommitter).remove(
-                Matchers.<InstanceIdentifier<Meter>>any(), Matchers.<Meter>any(),
-                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
-
-        Mockito.doAnswer(createSalServiceFutureAnswer()).when(tableCommitter).update(
-                Matchers.<InstanceIdentifier<TableFeatures>>any(), Matchers.<TableFeatures>any(), Matchers.<TableFeatures>any(),
-                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
-
-        reactor = new SyncReactorImpl();
-        reactor.setMeterForwarder(meterCommitter);
-        reactor.setTableForwarder(tableCommitter);
-        reactor.setGroupForwarder(groupCommitter);
-        reactor.setFlowForwarder(flowCommitter);
-        reactor.setTransactionService(flowCapableTxService);
-
-        counters = new SyncCrudCounters();
-    }
-
-    private <O> Answer<Future<RpcResult<O>>> createSalServiceFutureAnswer() {
-        return new Answer<Future<RpcResult<O>>>() {
-            @Override
-            public Future<RpcResult<O>> answer(final InvocationOnMock invocation) throws Throwable {
-                return RpcResultBuilder.<O>success().buildFuture();
-            }
-        };
-    }
-
-    private Group createGroup(final long groupIdValue) {
-        final Buckets buckets = new BucketsBuilder()
-                .setBucket(Collections.<Bucket>emptyList())
-                .build();
-        return new GroupBuilder()
-                .setGroupId(new GroupId(groupIdValue))
-                .setBuckets(buckets)
-                .build();
-    }
-
-    private Group createGroupWithAction(final long groupIdValue) {
-        final Buckets buckets = new BucketsBuilder()
-                .setBucket(Collections.singletonList(new BucketBuilder()
-                        .setAction(Collections.singletonList(new ActionBuilder()
-                                .setAction(new OutputActionCaseBuilder()
-                                        .setOutputAction(new OutputActionBuilder()
-                                                .setOutputNodeConnector(new Uri("ut-port-1"))
-                                                .build())
-                                        .build())
-                                .build()))
-                        .build()))
-                .build();
-        return new GroupBuilder()
-                .setGroupId(new GroupId(groupIdValue))
-                .setBuckets(buckets)
-                .build();
-    }
-
-    private Flow createFlow(final String flowIdValue, final int priority) {
-        return new FlowBuilder()
-                .setId(new FlowId(flowIdValue))
-                .setPriority(priority)
-                .setTableId((short) 42)
-                .setMatch(new MatchBuilder().build())
-                .build();
-    }
-
-    private Flow createFlowWithInstruction(final String flowIdValue, final int priority) {
-        return new FlowBuilder()
-                .setId(new FlowId(flowIdValue))
-                .setPriority(priority)
-                .setTableId((short) 42)
-                .setMatch(new MatchBuilder().build())
-                .setInstructions(new InstructionsBuilder()
-                        .setInstruction(Collections.singletonList(new InstructionBuilder()
-                                .setInstruction(new ApplyActionsCaseBuilder()
-                                        .setApplyActions(new ApplyActionsBuilder()
-                                                .setAction(Collections.singletonList(new ActionBuilder()
-                                                        .setAction(new OutputActionCaseBuilder()
-                                                                .setOutputAction(new OutputActionBuilder()
-                                                                        .setOutputNodeConnector(new Uri("ut-port-1"))
-                                                                        .build())
-                                                                .build())
-                                                        .build()))
-                                                .build())
-                                        .build())
-                                .build()))
-                        .build())
-                .build();
-    }
-
-    private Meter createMeter(final Long meterIdValue) {
-        return new MeterBuilder()
-                .setMeterId(new MeterId(meterIdValue))
-                .build();
-    }
-
-    private Meter createMeterWithBody(final Long meterIdValue) {
-        return new MeterBuilder()
-                .setMeterId(new MeterId(meterIdValue))
-                .setMeterBandHeaders(new MeterBandHeadersBuilder()
-                        .setMeterBandHeader(Collections.singletonList(new MeterBandHeaderBuilder()
-                                .setBandId(new BandId(42L))
-                                .setBandType(new DropBuilder()
-                                        .setDropRate(43L)
-                                        .build())
-                                .build()))
-                        .build())
-                .build();
-    }
-
-    private Group createGroupWithPreconditions(final long groupIdValue, final long... requiredId) {
-        final List<Action> actionBag = new ArrayList<>();
-        for (long groupIdPrecondition : requiredId) {
-            final GroupAction groupAction = new GroupActionBuilder()
-                    .setGroupId(groupIdPrecondition)
-                    .build();
-            final GroupActionCase groupActionCase = new GroupActionCaseBuilder()
-                    .setGroupAction(groupAction)
-                    .build();
-            final Action action = new ActionBuilder()
-                    .setAction(groupActionCase)
-                    .build();
-            actionBag.add(action);
-        }
-
-        final Bucket bucket = new BucketBuilder()
-                .setAction(actionBag)
-                .build();
-        final Buckets buckets = new BucketsBuilder()
-                .setBucket(Collections.singletonList(bucket))
-                .build();
-
-        return new GroupBuilder()
-                .setGroupId(new GroupId(groupIdValue))
-                .setBuckets(buckets)
-                .build();
-    }
-
-    @Test
-    public void testRemoveRedundantFlows() throws Exception {
-        Mockito.when(flowCommitter.remove(Matchers.<InstanceIdentifier<Flow>>any(), flowCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new RemoveFlowOutputBuilder().build()).buildFuture());
-
-        final Table tableCfg = new TableBuilder()
-                .setId((short) 0)
-                .setFlow(Arrays.asList(
-                        createFlow("f1", 1), createFlow("f2", 2)))
-                .build();
-        final FlowCapableNode config = new FlowCapableNodeBuilder()
-                .setTable(Collections.singletonList(tableCfg))
-                .build();
-
-        final Table tableOpe = new TableBuilder()
-                .setId((short) 0)
-                .setFlow(Arrays.asList(
-                        createFlow("f1", 1), createFlow("f2", 2), createFlow("f3", 3), createFlow("f4", 4)))
-                .build();
-        final FlowCapableNode operational = new FlowCapableNodeBuilder()
-                .setTable(Collections.singletonList(tableOpe))
-                .build();
-
-        final ListenableFuture<RpcResult<Void>> result = reactor.removeRedundantFlows(
-                NODE_IDENT, config, operational, counters);
-
-        Assert.assertTrue(result.isDone());
-        Assert.assertTrue(result.get().isSuccessful());
-
-        final List<Flow> flowCaptorAllValues = flowCaptor.getAllValues();
-        Assert.assertEquals(2, flowCaptorAllValues.size());
-        Assert.assertEquals("f3", flowCaptorAllValues.get(0).getId().getValue());
-        Assert.assertEquals("f4", flowCaptorAllValues.get(1).getId().getValue());
-
-        final InOrder inOrderFlow = Mockito.inOrder(flowCapableTxService, flowCommitter);
-        inOrderFlow.verify(flowCommitter, Mockito.times(2)).remove(Matchers.<InstanceIdentifier<Flow>>any(),
-                Matchers.<Flow>any(), Matchers.eq(NODE_IDENT));
-        inOrderFlow.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-        inOrderFlow.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testAddMissingFlows() throws Exception {
-        Mockito.when(flowCommitter.add(Matchers.<InstanceIdentifier<Flow>>any(), flowCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new AddFlowOutputBuilder().build()).buildFuture());
-
-        final Table tableCfg = new TableBuilder()
-                .setId((short) 0)
-                .setFlow(Arrays.asList(
-                        createFlow("f1", 1), createFlow("f2", 2), createFlow("f3", 3), createFlow("f4", 4)))
-                .build();
-        final FlowCapableNode config = new FlowCapableNodeBuilder()
-                .setTable(Collections.singletonList(tableCfg))
-                .build();
-
-        final Table tableOpe = new TableBuilder()
-                .setId((short) 0)
-                .setFlow(Arrays.asList(
-                        createFlow("f1", 1), createFlow("f2", 2)))
-                .build();
-        final FlowCapableNode operational = new FlowCapableNodeBuilder()
-                .setTable(Collections.singletonList(tableOpe))
-                .build();
-
-        final ListenableFuture<RpcResult<Void>> result = reactor.addMissingFlows(
-                NODE_IDENT, config, operational, counters);
-
-        Assert.assertTrue(result.isDone());
-        Assert.assertTrue(result.get().isSuccessful());
-
-        final List<Flow> flowCaptorAllValues = flowCaptor.getAllValues();
-        Assert.assertEquals(2, flowCaptorAllValues.size());
-        Assert.assertEquals("f3", flowCaptorAllValues.get(0).getId().getValue());
-        Assert.assertEquals("f4", flowCaptorAllValues.get(1).getId().getValue());
-
-        final InOrder inOrderFlow = Mockito.inOrder(flowCapableTxService, flowCommitter);
-        inOrderFlow.verify(flowCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Flow>>any(),
-                Matchers.<Flow>any(), Matchers.eq(NODE_IDENT));
-        //inOrderFlow.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-        inOrderFlow.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testAddMissingFlows_withUpdate() throws Exception {
-        Mockito.when(flowCommitter.add(Matchers.<InstanceIdentifier<Flow>>any(), flowCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new AddFlowOutputBuilder().build()).buildFuture());
-
-        Mockito.when(flowCommitter.update(Matchers.<InstanceIdentifier<Flow>>any(),
-                flowUpdateCaptor.capture(), flowUpdateCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new UpdateFlowOutputBuilder().build()).buildFuture());
-
-        final Table tableCfg = new TableBuilder()
-                .setId((short) 0)
-                .setFlow(Arrays.asList(
-                        createFlowWithInstruction("f1", 1), createFlow("f2", 2),
-                        createFlow("f3", 3), createFlow("f4", 4)))
-                .build();
-        final FlowCapableNode config = new FlowCapableNodeBuilder()
-                .setTable(Collections.singletonList(tableCfg))
-                .build();
-
-        final Table tableOpe = new TableBuilder()
-                .setId((short) 0)
-                .setFlow(Arrays.asList(
-                        createFlow("f1", 1), createFlow("f2", 2)))
-                .build();
-        final FlowCapableNode operational = new FlowCapableNodeBuilder()
-                .setTable(Collections.singletonList(tableOpe))
-                .build();
-
-        final ListenableFuture<RpcResult<Void>> result = reactor.addMissingFlows(
-                NODE_IDENT, config, operational, counters);
-
-        Assert.assertTrue(result.isDone());
-        Assert.assertTrue(result.get().isSuccessful());
-
-        final List<Flow> flowCaptorAllValues = flowCaptor.getAllValues();
-        Assert.assertEquals(2, flowCaptorAllValues.size());
-        Assert.assertEquals("f3", flowCaptorAllValues.get(0).getId().getValue());
-        Assert.assertEquals("f4", flowCaptorAllValues.get(1).getId().getValue());
-
-        final List<Flow> flowUpdateCaptorAllValues = flowUpdateCaptor.getAllValues();
-        Assert.assertEquals(2, flowUpdateCaptorAllValues.size());
-        Assert.assertEquals("f1", flowUpdateCaptorAllValues.get(0).getId().getValue());
-        Assert.assertEquals("f1", flowUpdateCaptorAllValues.get(1).getId().getValue());
-
-        final InOrder inOrderFlow = Mockito.inOrder(flowCapableTxService, flowCommitter);
-        // update f1
-        inOrderFlow.verify(flowCommitter).update(Matchers.<InstanceIdentifier<Flow>>any(),
-                Matchers.<Flow>any(), Matchers.<Flow>any(), Matchers.eq(NODE_IDENT));
-        // add f3, f4
-        inOrderFlow.verify(flowCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Flow>>any(),
-                Matchers.<Flow>any(), Matchers.eq(NODE_IDENT));
-        //inOrderFlow.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-
-        inOrderFlow.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testAddMissingMeters() throws Exception {
-        Mockito.when(meterCommitter.add(Matchers.<InstanceIdentifier<Meter>>any(), meterCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new AddMeterOutputBuilder().build()).buildFuture());
-
-        final FlowCapableNode config = new FlowCapableNodeBuilder()
-                .setMeter(Arrays.asList(
-                        createMeter(1L), createMeter(2L), createMeter(3L), createMeter(4L)
-                ))
-                .build();
-
-        final FlowCapableNode operational = new FlowCapableNodeBuilder()
-                .setMeter(Arrays.asList(
-                        createMeter(1L), createMeter(3L)
-                ))
-                .build();
-
-        final ListenableFuture<RpcResult<Void>> result = reactor.addMissingMeters(
-                NODE_IDENT, config, operational, counters);
-
-        Assert.assertTrue(result.isDone());
-        Assert.assertTrue(result.get().isSuccessful());
-
-        final List<Meter> metercaptorAllValues = meterCaptor.getAllValues();
-        Assert.assertEquals(2, metercaptorAllValues.size());
-        Assert.assertEquals(2L, metercaptorAllValues.get(0).getMeterId().getValue().longValue());
-        Assert.assertEquals(4L, metercaptorAllValues.get(1).getMeterId().getValue().longValue());
-
-        final InOrder inOrderMeter = Mockito.inOrder(flowCapableTxService, meterCommitter);
-        inOrderMeter.verify(meterCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Meter>>any(),
-                Matchers.<Meter>any(), Matchers.eq(NODE_IDENT));
-        //inOrderMeter.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-        inOrderMeter.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testAddMissingMeters_withUpdate() throws Exception {
-        Mockito.when(meterCommitter.add(Matchers.<InstanceIdentifier<Meter>>any(), meterCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new AddMeterOutputBuilder().build()).buildFuture());
-
-        Mockito.when(meterCommitter.update(Matchers.<InstanceIdentifier<Meter>>any(),
-                meterUpdateCaptor.capture(), meterUpdateCaptor.capture(), Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new UpdateMeterOutputBuilder().build()).buildFuture());
-
-        final FlowCapableNode config = new FlowCapableNodeBuilder()
-                .setMeter(Arrays.asList(
-                        createMeterWithBody(1L), createMeter(2L), createMeter(3L), createMeter(4L)
-                ))
-                .build();
-
-        final FlowCapableNode operational = new FlowCapableNodeBuilder()
-                .setMeter(Arrays.asList(
-                        createMeter(1L), createMeter(3L)
-                ))
-                .build();
-
-        final ListenableFuture<RpcResult<Void>> result = reactor.addMissingMeters(
-                NODE_IDENT, config, operational, counters);
-
-        Assert.assertTrue(result.isDone());
-        Assert.assertTrue(result.get().isSuccessful());
-
-        final List<Meter> meterCaptorAllValues = meterCaptor.getAllValues();
-        Assert.assertEquals(2, meterCaptorAllValues.size());
-        Assert.assertEquals(2L, meterCaptorAllValues.get(0).getMeterId().getValue().longValue());
-        Assert.assertEquals(4L, meterCaptorAllValues.get(1).getMeterId().getValue().longValue());
-
-
-        final List<Meter> meterUpdateCaptorAllValues = meterUpdateCaptor.getAllValues();
-        Assert.assertEquals(2, meterUpdateCaptorAllValues.size());
-        Assert.assertEquals(1L, meterUpdateCaptorAllValues.get(0).getMeterId().getValue().longValue());
-        Assert.assertEquals(1L, meterUpdateCaptorAllValues.get(1).getMeterId().getValue().longValue());
-
-        final InOrder inOrderMeters = Mockito.inOrder(flowCapableTxService, meterCommitter);
-        inOrderMeters.verify(meterCommitter).update(Matchers.<InstanceIdentifier<Meter>>any(),
-                Matchers.<Meter>any(), Matchers.<Meter>any(), Matchers.eq(NODE_IDENT));
-        inOrderMeters.verify(meterCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Meter>>any(),
-                Matchers.<Meter>any(), Matchers.eq(NODE_IDENT));
-        //inOrderMeters.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-
-        inOrderMeters.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testRemoveRedundantMeters() throws Exception {
-        Mockito.when(meterCommitter.remove(Matchers.<InstanceIdentifier<Meter>>any(), meterCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new RemoveMeterOutputBuilder().build()).buildFuture());
-
-        final FlowCapableNode config = new FlowCapableNodeBuilder()
-                .setMeter(Arrays.asList(
-                        createMeter(1L), createMeter(3L)
-                ))
-                .build();
-
-        final FlowCapableNode operational = new FlowCapableNodeBuilder()
-                .setMeter(Arrays.asList(
-                        createMeter(1L), createMeter(2L), createMeter(3L), createMeter(4L)
-                ))
-                .build();
-
-        final ListenableFuture<RpcResult<Void>> result = reactor.removeRedundantMeters(
-                NODE_IDENT, config, operational, counters);
-
-        Assert.assertTrue(result.isDone());
-        Assert.assertTrue(result.get().isSuccessful());
-
-        final List<Meter> metercaptorAllValues = meterCaptor.getAllValues();
-        Assert.assertEquals(2, metercaptorAllValues.size());
-        Assert.assertEquals(2L, metercaptorAllValues.get(0).getMeterId().getValue().longValue());
-        Assert.assertEquals(4L, metercaptorAllValues.get(1).getMeterId().getValue().longValue());
-
-        final InOrder inOrderMeter = Mockito.inOrder(flowCapableTxService, meterCommitter);
-        inOrderMeter.verify(meterCommitter, Mockito.times(2)).remove(Matchers.<InstanceIdentifier<Meter>>any(),
-                Matchers.<Meter>any(), Matchers.eq(NODE_IDENT));
-        //inOrderMeter.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-        inOrderMeter.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testAddMissingGroups() throws Exception {
-        Mockito.when(groupCommitter.add(Matchers.<InstanceIdentifier<Group>>any(), groupCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new AddGroupOutputBuilder().build()).buildFuture());
-
-        final FlowCapableNode config = new FlowCapableNodeBuilder()
-                .setGroup(Arrays.asList(
-                        createGroup(1L), createGroup(2L),
-                        createGroupWithPreconditions(3L, 2L),
-                        createGroupWithPreconditions(4L, 2L),
-                        createGroupWithPreconditions(5L, 3L, 4L)))
-                .build();
-        final FlowCapableNode operational = new FlowCapableNodeBuilder()
-                .setGroup(Collections.singletonList(createGroup(1L)))
-                .build();
-
-        final ListenableFuture<RpcResult<Void>> result = reactor.addMissingGroups(
-                NODE_IDENT, config, operational, counters);
-
-        Assert.assertTrue(result.isDone());
-        Assert.assertTrue(result.get().isSuccessful());
-
-        final List<Group> groupCaptorAllValues = groupCaptor.getAllValues();
-        Assert.assertEquals(4, groupCaptorAllValues.size());
-        Assert.assertEquals(2L, groupCaptorAllValues.get(0).getGroupId().getValue().longValue());
-        Assert.assertEquals(3L, groupCaptorAllValues.get(1).getGroupId().getValue().longValue());
-        Assert.assertEquals(4L, groupCaptorAllValues.get(2).getGroupId().getValue().longValue());
-        Assert.assertEquals(5L, groupCaptorAllValues.get(3).getGroupId().getValue().longValue());
-
-        final InOrder inOrderGroups = Mockito.inOrder(flowCapableTxService, groupCommitter);
-        // add 2
-        inOrderGroups.verify(groupCommitter).add(Matchers.<InstanceIdentifier<Group>>any(),
-                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
-        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-        // add 3, 4
-        inOrderGroups.verify(groupCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Group>>any(),
-                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
-        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-        // add 5
-        inOrderGroups.verify(groupCommitter).add(Matchers.<InstanceIdentifier<Group>>any(),
-                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
-        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-
-        inOrderGroups.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testAddMissingGroups_withUpdate() throws Exception {
-        Mockito.when(groupCommitter.add(Matchers.<InstanceIdentifier<Group>>any(), groupCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new AddGroupOutputBuilder().build()).buildFuture());
-
-        Mockito.when(groupCommitter.update(Matchers.<InstanceIdentifier<Group>>any(),
-                groupUpdateCaptor.capture(), groupUpdateCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new UpdateGroupOutputBuilder().build()).buildFuture());
-
-        final FlowCapableNode config = new FlowCapableNodeBuilder()
-                .setGroup(Arrays.asList(
-                        createGroupWithAction(1L), createGroup(2L),
-                        createGroupWithPreconditions(3L, 2L),
-                        createGroupWithPreconditions(4L, 2L),
-                        createGroupWithPreconditions(5L, 3L, 4L)))
-                .build();
-        final FlowCapableNode operational = new FlowCapableNodeBuilder()
-                .setGroup(Collections.singletonList(createGroup(1L)))
-                .build();
-
-        final ListenableFuture<RpcResult<Void>> result = reactor.addMissingGroups(
-                NODE_IDENT, config, operational, counters);
-
-        Assert.assertTrue(result.isDone());
-        Assert.assertTrue(result.get().isSuccessful());
-
-        final List<Group> groupCaptorAllValues = groupCaptor.getAllValues();
-        Assert.assertEquals(4, groupCaptorAllValues.size());
-        Assert.assertEquals(2L, groupCaptorAllValues.get(0).getGroupId().getValue().longValue());
-        Assert.assertEquals(3L, groupCaptorAllValues.get(1).getGroupId().getValue().longValue());
-        Assert.assertEquals(4L, groupCaptorAllValues.get(2).getGroupId().getValue().longValue());
-        Assert.assertEquals(5L, groupCaptorAllValues.get(3).getGroupId().getValue().longValue());
-
-        final List<Group> groupUpdateCaptorAllValues = groupUpdateCaptor.getAllValues();
-        Assert.assertEquals(2, groupUpdateCaptorAllValues.size());
-        Assert.assertEquals(1L, groupUpdateCaptorAllValues.get(0).getGroupId().getValue().longValue());
-        Assert.assertEquals(1L, groupUpdateCaptorAllValues.get(1).getGroupId().getValue().longValue());
-
-        final InOrder inOrderGroups = Mockito.inOrder(flowCapableTxService, groupCommitter);
-
-        // add 2, update 1
-        inOrderGroups.verify(groupCommitter).add(Matchers.<InstanceIdentifier<Group>>any(),
-                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
-        inOrderGroups.verify(groupCommitter).update(Matchers.<InstanceIdentifier<Group>>any(),
-                Matchers.<Group>any(), Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
-        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-
-        // add 3, 4
-        inOrderGroups.verify(groupCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Group>>any(),
-                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
-        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-        // add 5
-        inOrderGroups.verify(groupCommitter).add(Matchers.<InstanceIdentifier<Group>>any(),
-                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
-        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-
-        inOrderGroups.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testRemoveRedundantGroups() throws Exception {
-        Mockito.when(groupCommitter.remove(Matchers.<InstanceIdentifier<Group>>any(), groupCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new RemoveGroupOutputBuilder().build()).buildFuture());
-
-        final FlowCapableNode operational = new FlowCapableNodeBuilder()
-                .setGroup(new ArrayList<>(Arrays.asList(
-                        createGroup(1L), createGroup(2L),
-                        createGroupWithPreconditions(3L, 2L),
-                        createGroupWithPreconditions(4L, 2L),
-                        createGroupWithPreconditions(5L, 3L, 4L))))
-                .build();
-        final FlowCapableNode config = new FlowCapableNodeBuilder()
-                .setGroup(Collections.singletonList(createGroup(1L)))
-                .build();
-
-        final ListenableFuture<RpcResult<Void>> result = reactor.removeRedundantGroups(
-                NODE_IDENT, config, operational, counters);
-
-        Assert.assertTrue(result.isDone());
-        Assert.assertTrue(result.get().isSuccessful());
-
-        final List<Group> groupCaptorAllValues = groupCaptor.getAllValues();
-        Assert.assertEquals(4, groupCaptorAllValues.size());
-        Assert.assertEquals(5L, groupCaptorAllValues.get(0).getGroupId().getValue().longValue());
-        Assert.assertEquals(3L, groupCaptorAllValues.get(1).getGroupId().getValue().longValue());
-        Assert.assertEquals(4L, groupCaptorAllValues.get(2).getGroupId().getValue().longValue());
-        Assert.assertEquals(2L, groupCaptorAllValues.get(3).getGroupId().getValue().longValue());
-
-        final InOrder inOrderGroup = Mockito.inOrder(flowCapableTxService, groupCommitter);
-        // remove 5
-        inOrderGroup.verify(groupCommitter).remove(Matchers.<InstanceIdentifier<Group>>any(),
-                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
-        inOrderGroup.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-        // remove 3, 4
-        inOrderGroup.verify(groupCommitter, Mockito.times(2)).remove(Matchers.<InstanceIdentifier<Group>>any(),
-                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
-        inOrderGroup.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-        // remove 2
-        inOrderGroup.verify(groupCommitter).remove(Matchers.<InstanceIdentifier<Group>>any(),
-                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
-        inOrderGroup.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-
-        inOrderGroup.verifyNoMoreInteractions();
+        reactor = new SyncReactorImpl(syncPlanPushStrategy);
     }
 
     @Test
     public void testSyncup() throws Exception {
-        final FlowCapableNode configFcn = new FlowCapableNodeBuilder()
-                .setGroup(Collections.singletonList(createGroup(1L)))
-                .setTable(Collections.singletonList(new TableBuilder()
-                        .setFlow(Collections.singletonList(createFlow("f1", 1)))
-                        .build()))
-                .setMeter(Collections.singletonList(createMeter(1L)))
-                .build();
-
-        final FlowCapableNode operationalFcn = new FlowCapableNodeBuilder()
-                .setGroup(Collections.singletonList(createGroup(2L)))
-                .setTable(Collections.singletonList(new TableBuilder()
-                        .setFlow(Collections.singletonList(createFlow("f2", 2)))
-                        .build()))
-                .setMeter(Collections.singletonList(createMeter(2L)))
-                .build();
-
-        final ListenableFuture<Boolean> syncupResult = reactor.syncup(NODE_IDENT, configFcn, operationalFcn);
-        try {
-            final Boolean voidRpcResult = syncupResult.get(2, TimeUnit.SECONDS);
-            Assert.assertTrue(voidRpcResult);
-
-            final InOrder inOrder = Mockito.inOrder(flowCommitter, meterCommitter, groupCommitter,
-                    tableCommitter, flowCapableTxService);
-
-            inOrder.verify(groupCommitter).add(Matchers.<InstanceIdentifier<Group>>any(),
-                    Matchers.<Group>any(), Matchers.same(NODE_IDENT));
-            inOrder.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-            inOrder.verify(meterCommitter).add(Matchers.<InstanceIdentifier<Meter>>any(),
-                    Matchers.<Meter>any(), Matchers.same(NODE_IDENT));
-            //inOrder.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-            inOrder.verify(flowCommitter).add(Matchers.<InstanceIdentifier<Flow>>any(),
-                    Matchers.<Flow>any(), Matchers.same(NODE_IDENT));
-            //inOrder.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-
-            inOrder.verify(flowCommitter).remove(Matchers.<InstanceIdentifier<Flow>>any(),
-                    Matchers.<Flow>any(), Matchers.same(NODE_IDENT));
-            inOrder.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-            inOrder.verify(meterCommitter).remove(Matchers.<InstanceIdentifier<Meter>>any(),
-                    Matchers.<Meter>any(), Matchers.same(NODE_IDENT));
-            //inOrder.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-            inOrder.verify(groupCommitter).remove(Matchers.<InstanceIdentifier<Group>>any(),
-                    Matchers.<Group>any(), Matchers.same(NODE_IDENT));
-            inOrder.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
-
-            inOrder.verifyNoMoreInteractions();
-
-        } catch (Exception e) {
-            LOG.warn("syncup failed", e);
-            Assert.fail("syncup failed: " + e.getMessage());
-        }
-    }
-
-    @Test
-    public void testUpdateTableFeatures() throws Exception {
-        Mockito.when(tableCommitter.update(Matchers.<InstanceIdentifier<TableFeatures>>any(),
-                Matchers.isNull(TableFeatures.class), tableFeaturesCaptor.capture(),
-                Matchers.same(NODE_IDENT)))
-                .thenReturn(RpcResultBuilder.success(new UpdateTableOutputBuilder().build()).buildFuture());
-
-        final FlowCapableNode operational = new FlowCapableNodeBuilder()
-                .setTable(Collections.singletonList(new TableBuilder()
-                        .setId((short) 1)
-                        .build()))
-                .setTableFeatures(Collections.singletonList(new TableFeaturesBuilder()
-                        .setName("test table features")
-                        .setTableId((short) 1)
-                        .build()))
-                .build();
-
-        final ListenableFuture<RpcResult<Void>> result = reactor.updateTableFeatures(
-                NODE_IDENT, operational);
-
-        Assert.assertTrue(result.isDone());
-        Assert.assertTrue(result.get().isSuccessful());
-
-        final List<TableFeatures> groupCaptorAllValues = tableFeaturesCaptor.getAllValues();
-        Assert.assertEquals(0, groupCaptorAllValues.size());
-        //Assert.assertEquals("test table features", groupCaptorAllValues.get(0).getName());
-        //Assert.assertEquals(1, groupCaptorAllValues.get(0).getTableId().intValue());
-
-        Mockito.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+        // TODO: add test body as soon as strategies are settled
     }
 }
\ No newline at end of file
diff --git a/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/strategy/SyncPlanPushStrategyIncrementalImplTest.java b/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/strategy/SyncPlanPushStrategyIncrementalImplTest.java
new file mode 100644 (file)
index 0000000..75de98f
--- /dev/null
@@ -0,0 +1,725 @@
+/**
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. 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.openflowplugin.applications.frsync.impl.strategy;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.openflowplugin.applications.frsync.impl.FlowForwarder;
+import org.opendaylight.openflowplugin.applications.frsync.impl.GroupForwarder;
+import org.opendaylight.openflowplugin.applications.frsync.impl.MeterForwarder;
+import org.opendaylight.openflowplugin.applications.frsync.impl.TableForwarder;
+import org.opendaylight.openflowplugin.applications.frsync.util.ItemSyncBox;
+import org.opendaylight.openflowplugin.applications.frsync.util.SyncCrudCounters;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.GroupActionCase;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.GroupActionCaseBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.OutputActionCaseBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.group.action._case.GroupAction;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.group.action._case.GroupActionBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.output.action._case.OutputActionBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.MeterBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableBuilder;
+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.FlowBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.FlowCapableTransactionService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.SendBarrierInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.AddGroupOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.RemoveGroupOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.UpdateGroupOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.GroupId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group.Buckets;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group.BucketsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group.buckets.Bucket;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.group.buckets.BucketBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.Group;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.GroupBuilder;
+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.meter.service.rev130918.AddMeterOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.RemoveMeterOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.UpdateMeterOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.BandId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.MeterId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.band.type.band.type.DropBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.meter.MeterBandHeadersBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.meter.meter.band.headers.MeterBandHeaderBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.service.rev131026.UpdateTableOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeatures;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeaturesBuilder;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Future;
+
+/**
+ * Test for {@link SyncPlanPushStrategyIncrementalImpl}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class SyncPlanPushStrategyIncrementalImplTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SyncPlanPushStrategyIncrementalImplTest.class);
+
+    private static final NodeId NODE_ID = new NodeId("unit-nodeId");
+    private static final InstanceIdentifier<FlowCapableNode> NODE_IDENT = InstanceIdentifier.create(Nodes.class)
+            .child(Node.class, new NodeKey(NODE_ID)).augmentation(FlowCapableNode.class);
+
+    private SyncPlanPushStrategyIncrementalImpl syncPlanPushStrategy;
+
+    @Mock
+    private DataBroker db;
+    @Mock
+    private GroupForwarder groupCommitter;
+    @Mock
+    private FlowForwarder flowCommitter;
+    @Mock
+    private MeterForwarder meterCommitter;
+    @Mock
+    private TableForwarder tableCommitter;
+    @Mock
+    private FlowCapableTransactionService flowCapableTxService;
+
+    @Captor
+    private ArgumentCaptor<Group> groupCaptor;
+    @Captor
+    private ArgumentCaptor<Group> groupUpdateCaptor;
+    @Captor
+    private ArgumentCaptor<Flow> flowCaptor;
+    @Captor
+    private ArgumentCaptor<Flow> flowUpdateCaptor;
+    @Captor
+    private ArgumentCaptor<Meter> meterCaptor;
+    @Captor
+    private ArgumentCaptor<Meter> meterUpdateCaptor;
+    @Captor
+    private ArgumentCaptor<TableFeatures> tableFeaturesCaptor;
+
+    private SyncCrudCounters counters;
+
+    @Test
+    public void testExecuteSyncStrategy() throws Exception {
+
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        Mockito.when(flowCapableTxService.sendBarrier(Matchers.<SendBarrierInput>any()))
+                .thenReturn(RpcResultBuilder.success((Void) null).buildFuture());
+
+        Mockito.doAnswer(createSalServiceFutureAnswer()).when(groupCommitter).add(
+                Matchers.<InstanceIdentifier<Group>>any(), Matchers.<Group>any(),
+                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
+        Mockito.doAnswer(createSalServiceFutureAnswer()).when(groupCommitter).update(
+                Matchers.<InstanceIdentifier<Group>>any(), Matchers.<Group>any(), Matchers.<Group>any(),
+                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
+        Mockito.doAnswer(createSalServiceFutureAnswer()).when(groupCommitter).remove(
+                Matchers.<InstanceIdentifier<Group>>any(), Matchers.<Group>any(),
+                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
+
+        Mockito.doAnswer(createSalServiceFutureAnswer()).when(flowCommitter).add(
+                Matchers.<InstanceIdentifier<Flow>>any(), Matchers.<Flow>any(),
+                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
+        Mockito.doAnswer(createSalServiceFutureAnswer()).when(flowCommitter).update(
+                Matchers.<InstanceIdentifier<Flow>>any(), Matchers.<Flow>any(), Matchers.<Flow>any(),
+                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
+        Mockito.doAnswer(createSalServiceFutureAnswer()).when(flowCommitter).remove(
+                Matchers.<InstanceIdentifier<Flow>>any(), Matchers.<Flow>any(),
+                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
+
+        Mockito.doAnswer(createSalServiceFutureAnswer()).when(meterCommitter).add(
+                Matchers.<InstanceIdentifier<Meter>>any(), Matchers.<Meter>any(),
+                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
+        Mockito.doAnswer(createSalServiceFutureAnswer()).when(meterCommitter).update(
+                Matchers.<InstanceIdentifier<Meter>>any(), Matchers.<Meter>any(), Matchers.<Meter>any(),
+                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
+        Mockito.doAnswer(createSalServiceFutureAnswer()).when(meterCommitter).remove(
+                Matchers.<InstanceIdentifier<Meter>>any(), Matchers.<Meter>any(),
+                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
+
+        Mockito.doAnswer(createSalServiceFutureAnswer()).when(tableCommitter).update(
+                Matchers.<InstanceIdentifier<TableFeatures>>any(), Matchers.<TableFeatures>any(), Matchers.<TableFeatures>any(),
+                Matchers.<InstanceIdentifier<FlowCapableNode>>any());
+
+        syncPlanPushStrategy = new SyncPlanPushStrategyIncrementalImpl()
+                .setMeterForwarder(meterCommitter)
+                .setTableForwarder(tableCommitter)
+                .setGroupForwarder(groupCommitter)
+                .setFlowForwarder(flowCommitter)
+                .setTransactionService(flowCapableTxService);
+
+        counters = new SyncCrudCounters();
+    }
+
+    private <O> Answer<Future<RpcResult<O>>> createSalServiceFutureAnswer() {
+        return new Answer<Future<RpcResult<O>>>() {
+            @Override
+            public Future<RpcResult<O>> answer(final InvocationOnMock invocation) throws Throwable {
+                return RpcResultBuilder.<O>success().buildFuture();
+            }
+        };
+    }
+
+    private Group createGroup(final long groupIdValue) {
+        final Buckets buckets = new BucketsBuilder()
+                .setBucket(Collections.<Bucket>emptyList())
+                .build();
+        return new GroupBuilder()
+                .setGroupId(new GroupId(groupIdValue))
+                .setBuckets(buckets)
+                .build();
+    }
+
+    private Group createGroupWithAction(final long groupIdValue) {
+        final Buckets buckets = new BucketsBuilder()
+                .setBucket(Collections.singletonList(new BucketBuilder()
+                        .setAction(Collections.singletonList(new ActionBuilder()
+                                .setAction(new OutputActionCaseBuilder()
+                                        .setOutputAction(new OutputActionBuilder()
+                                                .setOutputNodeConnector(new Uri("ut-port-1"))
+                                                .build())
+                                        .build())
+                                .build()))
+                        .build()))
+                .build();
+        return new GroupBuilder()
+                .setGroupId(new GroupId(groupIdValue))
+                .setBuckets(buckets)
+                .build();
+    }
+
+    private Flow createFlow(final String flowIdValue, final int priority) {
+        return new FlowBuilder()
+                .setId(new FlowId(flowIdValue))
+                .setPriority(priority)
+                .setTableId((short) 42)
+                .setMatch(new MatchBuilder().build())
+                .build();
+    }
+
+    private Flow createFlowWithInstruction(final String flowIdValue, final int priority) {
+        return new FlowBuilder()
+                .setId(new FlowId(flowIdValue))
+                .setPriority(priority)
+                .setTableId((short) 42)
+                .setMatch(new MatchBuilder().build())
+                .setInstructions(new InstructionsBuilder()
+                        .setInstruction(Collections.singletonList(new InstructionBuilder()
+                                .setInstruction(new ApplyActionsCaseBuilder()
+                                        .setApplyActions(new ApplyActionsBuilder()
+                                                .setAction(Collections.singletonList(new ActionBuilder()
+                                                        .setAction(new OutputActionCaseBuilder()
+                                                                .setOutputAction(new OutputActionBuilder()
+                                                                        .setOutputNodeConnector(new Uri("ut-port-1"))
+                                                                        .build())
+                                                                .build())
+                                                        .build()))
+                                                .build())
+                                        .build())
+                                .build()))
+                        .build())
+                .build();
+    }
+
+    private Meter createMeter(final Long meterIdValue) {
+        return new MeterBuilder()
+                .setMeterId(new MeterId(meterIdValue))
+                .build();
+    }
+
+    private Meter createMeterWithBody(final Long meterIdValue) {
+        return new MeterBuilder()
+                .setMeterId(new MeterId(meterIdValue))
+                .setMeterBandHeaders(new MeterBandHeadersBuilder()
+                        .setMeterBandHeader(Collections.singletonList(new MeterBandHeaderBuilder()
+                                .setBandId(new BandId(42L))
+                                .setBandType(new DropBuilder()
+                                        .setDropRate(43L)
+                                        .build())
+                                .build()))
+                        .build())
+                .build();
+    }
+
+    private Group createGroupWithPreconditions(final long groupIdValue, final long... requiredId) {
+        final List<Action> actionBag = new ArrayList<>();
+        for (long groupIdPrecondition : requiredId) {
+            final GroupAction groupAction = new GroupActionBuilder()
+                    .setGroupId(groupIdPrecondition)
+                    .build();
+            final GroupActionCase groupActionCase = new GroupActionCaseBuilder()
+                    .setGroupAction(groupAction)
+                    .build();
+            final Action action = new ActionBuilder()
+                    .setAction(groupActionCase)
+                    .build();
+            actionBag.add(action);
+        }
+
+        final Bucket bucket = new BucketBuilder()
+                .setAction(actionBag)
+                .build();
+        final Buckets buckets = new BucketsBuilder()
+                .setBucket(Collections.singletonList(bucket))
+                .build();
+
+        return new GroupBuilder()
+                .setGroupId(new GroupId(groupIdValue))
+                .setBuckets(buckets)
+                .build();
+    }
+
+    @Test
+    public void testAddMissingFlows() throws Exception {
+        Mockito.when(flowCommitter.add(Matchers.<InstanceIdentifier<Flow>>any(), flowCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new AddFlowOutputBuilder().build()).buildFuture());
+
+        final ItemSyncBox<Flow> flowBox = new ItemSyncBox<>();
+        flowBox.getItemsToPush().add(createFlow("f3", 3));
+        flowBox.getItemsToPush().add(createFlow("f4", 4));
+
+        final Map<TableKey, ItemSyncBox<Flow>> flowBoxMap = new LinkedHashMap<>();
+        flowBoxMap.put(new TableKey((short) 0), flowBox);
+
+        final ListenableFuture<RpcResult<Void>> result = syncPlanPushStrategy.addMissingFlows(
+                NODE_ID, NODE_IDENT, flowBoxMap, counters);
+
+        Assert.assertTrue(result.isDone());
+        Assert.assertTrue(result.get().isSuccessful());
+
+        final List<Flow> flowCaptorAllValues = flowCaptor.getAllValues();
+        Assert.assertEquals(2, flowCaptorAllValues.size());
+        Assert.assertEquals("f3", flowCaptorAllValues.get(0).getId().getValue());
+        Assert.assertEquals("f4", flowCaptorAllValues.get(1).getId().getValue());
+
+        final InOrder inOrderFlow = Mockito.inOrder(flowCapableTxService, flowCommitter);
+        inOrderFlow.verify(flowCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Flow>>any(),
+                Matchers.<Flow>any(), Matchers.eq(NODE_IDENT));
+        //TODO: uncomment when enabled in impl
+//        inOrderFlow.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+        inOrderFlow.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testRemoveRedundantFlows() throws Exception {
+        Mockito.when(flowCommitter.remove(Matchers.<InstanceIdentifier<Flow>>any(), flowCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new RemoveFlowOutputBuilder().build()).buildFuture());
+
+        final ItemSyncBox<Flow> flowBox = new ItemSyncBox<>();
+        flowBox.getItemsToPush().add(createFlow("f3", 3));
+        flowBox.getItemsToPush().add(createFlow("f4", 4));
+
+        final Map<TableKey, ItemSyncBox<Flow>> flowBoxMap = new LinkedHashMap<>();
+        flowBoxMap.put(new TableKey((short) 0), flowBox);
+
+        final ListenableFuture<RpcResult<Void>> result = syncPlanPushStrategy.removeRedundantFlows(
+                NODE_ID, NODE_IDENT, flowBoxMap, counters);
+
+        Assert.assertTrue(result.isDone());
+        Assert.assertTrue(result.get().isSuccessful());
+
+        final List<Flow> flowCaptorAllValues = flowCaptor.getAllValues();
+        Assert.assertEquals(2, flowCaptorAllValues.size());
+        Assert.assertEquals("f3", flowCaptorAllValues.get(0).getId().getValue());
+        Assert.assertEquals("f4", flowCaptorAllValues.get(1).getId().getValue());
+
+        final InOrder inOrderFlow = Mockito.inOrder(flowCapableTxService, flowCommitter);
+        inOrderFlow.verify(flowCommitter, Mockito.times(2)).remove(Matchers.<InstanceIdentifier<Flow>>any(),
+                Matchers.<Flow>any(), Matchers.eq(NODE_IDENT));
+        inOrderFlow.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+        inOrderFlow.verifyNoMoreInteractions();
+    }
+
+
+    @Test
+    public void testAddMissingFlows_withUpdate() throws Exception {
+        Mockito.when(flowCommitter.add(Matchers.<InstanceIdentifier<Flow>>any(), flowCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new AddFlowOutputBuilder().build()).buildFuture());
+
+        Mockito.when(flowCommitter.update(Matchers.<InstanceIdentifier<Flow>>any(),
+                flowUpdateCaptor.capture(), flowUpdateCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new UpdateFlowOutputBuilder().build()).buildFuture());
+
+        final ItemSyncBox<Flow> flowBox = new ItemSyncBox<>();
+        flowBox.getItemsToPush().add(createFlow("f3", 3));
+        flowBox.getItemsToPush().add(createFlow("f4", 4));
+        flowBox.getItemsToUpdate().add(new ItemSyncBox.ItemUpdateTuple<>(
+                createFlow("f1", 1), createFlowWithInstruction("f1", 1)));
+
+        final Map<TableKey, ItemSyncBox<Flow>> flowBoxMap = new LinkedHashMap<>();
+        flowBoxMap.put(new TableKey((short) 0), flowBox);
+
+
+        //TODO: replace null
+        final ListenableFuture<RpcResult<Void>> result = syncPlanPushStrategy.addMissingFlows(
+                NODE_ID, NODE_IDENT, flowBoxMap, counters);
+
+        Assert.assertTrue(result.isDone());
+        Assert.assertTrue(result.get().isSuccessful());
+
+        final List<Flow> flowCaptorAllValues = flowCaptor.getAllValues();
+        Assert.assertEquals(2, flowCaptorAllValues.size());
+        Assert.assertEquals("f3", flowCaptorAllValues.get(0).getId().getValue());
+        Assert.assertEquals("f4", flowCaptorAllValues.get(1).getId().getValue());
+
+        final List<Flow> flowUpdateCaptorAllValues = flowUpdateCaptor.getAllValues();
+        Assert.assertEquals(2, flowUpdateCaptorAllValues.size());
+        Assert.assertEquals("f1", flowUpdateCaptorAllValues.get(0).getId().getValue());
+        Assert.assertEquals("f1", flowUpdateCaptorAllValues.get(1).getId().getValue());
+
+        final InOrder inOrderFlow = Mockito.inOrder(flowCapableTxService, flowCommitter);
+        // add f3, f4
+        inOrderFlow.verify(flowCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Flow>>any(),
+                Matchers.<Flow>any(), Matchers.eq(NODE_IDENT));
+        // update f1
+        inOrderFlow.verify(flowCommitter).update(Matchers.<InstanceIdentifier<Flow>>any(),
+                Matchers.<Flow>any(), Matchers.<Flow>any(), Matchers.eq(NODE_IDENT));
+        //TODO: uncomment when enabled in impl
+//        inOrderFlow.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+
+        inOrderFlow.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testAddMissingMeters() throws Exception {
+        Mockito.when(meterCommitter.add(Matchers.<InstanceIdentifier<Meter>>any(), meterCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new AddMeterOutputBuilder().build()).buildFuture());
+
+        final ItemSyncBox<Meter> meterSyncBox = new ItemSyncBox<>();
+        meterSyncBox.getItemsToPush().add(createMeter(2L));
+        meterSyncBox.getItemsToPush().add(createMeter(4L));
+
+        final ListenableFuture<RpcResult<Void>> result = syncPlanPushStrategy.addMissingMeters(
+                NODE_ID, NODE_IDENT, meterSyncBox, counters);
+
+        Assert.assertTrue(result.isDone());
+        Assert.assertTrue(result.get().isSuccessful());
+
+        final List<Meter> metercaptorAllValues = meterCaptor.getAllValues();
+        Assert.assertEquals(2, metercaptorAllValues.size());
+        Assert.assertEquals(2L, metercaptorAllValues.get(0).getMeterId().getValue().longValue());
+        Assert.assertEquals(4L, metercaptorAllValues.get(1).getMeterId().getValue().longValue());
+
+        final InOrder inOrderMeter = Mockito.inOrder(flowCapableTxService, meterCommitter);
+        inOrderMeter.verify(meterCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Meter>>any(),
+                Matchers.<Meter>any(), Matchers.eq(NODE_IDENT));
+        //TODO: uncomment when enabled in impl
+//        inOrderMeter.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+        inOrderMeter.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testAddMissingMeters_withUpdate() throws Exception {
+        Mockito.when(meterCommitter.add(Matchers.<InstanceIdentifier<Meter>>any(), meterCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new AddMeterOutputBuilder().build()).buildFuture());
+
+        Mockito.when(meterCommitter.update(Matchers.<InstanceIdentifier<Meter>>any(),
+                meterUpdateCaptor.capture(), meterUpdateCaptor.capture(), Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new UpdateMeterOutputBuilder().build()).buildFuture());
+
+        final ItemSyncBox<Meter> meterSyncBox = new ItemSyncBox<>();
+        meterSyncBox.getItemsToPush().add(createMeter(2L));
+        meterSyncBox.getItemsToPush().add(createMeter(4L));
+        meterSyncBox.getItemsToUpdate().add(new ItemSyncBox.ItemUpdateTuple<>(
+                createMeter(1L), createMeterWithBody(1L)));
+
+        final ListenableFuture<RpcResult<Void>> result = syncPlanPushStrategy.addMissingMeters(
+                NODE_ID, NODE_IDENT, meterSyncBox, counters);
+
+        Assert.assertTrue(result.isDone());
+        Assert.assertTrue(result.get().isSuccessful());
+
+        final List<Meter> meterCaptorAllValues = meterCaptor.getAllValues();
+        Assert.assertEquals(2, meterCaptorAllValues.size());
+        Assert.assertEquals(2L, meterCaptorAllValues.get(0).getMeterId().getValue().longValue());
+        Assert.assertEquals(4L, meterCaptorAllValues.get(1).getMeterId().getValue().longValue());
+
+
+        final List<Meter> meterUpdateCaptorAllValues = meterUpdateCaptor.getAllValues();
+        Assert.assertEquals(2, meterUpdateCaptorAllValues.size());
+        Assert.assertEquals(1L, meterUpdateCaptorAllValues.get(0).getMeterId().getValue().longValue());
+        Assert.assertEquals(1L, meterUpdateCaptorAllValues.get(1).getMeterId().getValue().longValue());
+
+        final InOrder inOrderMeters = Mockito.inOrder(flowCapableTxService, meterCommitter);
+        inOrderMeters.verify(meterCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Meter>>any(),
+                Matchers.<Meter>any(), Matchers.eq(NODE_IDENT));
+        inOrderMeters.verify(meterCommitter).update(Matchers.<InstanceIdentifier<Meter>>any(),
+                Matchers.<Meter>any(), Matchers.<Meter>any(), Matchers.eq(NODE_IDENT));
+        //TODO: uncomment when enabled in impl
+//        inOrderMeters.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+
+        inOrderMeters.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testRemoveRedundantMeters() throws Exception {
+        Mockito.when(meterCommitter.remove(Matchers.<InstanceIdentifier<Meter>>any(), meterCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new RemoveMeterOutputBuilder().build()).buildFuture());
+
+        final ItemSyncBox<Meter> meterSyncBox = new ItemSyncBox<>();
+        meterSyncBox.getItemsToPush().add(createMeter(2L));
+        meterSyncBox.getItemsToPush().add(createMeter(4L));
+        meterSyncBox.getItemsToUpdate().add(new ItemSyncBox.ItemUpdateTuple<>(
+                createMeter(1L), createMeterWithBody(1L)));
+
+        final ListenableFuture<RpcResult<Void>> result = syncPlanPushStrategy.removeRedundantMeters(
+                NODE_ID, NODE_IDENT, meterSyncBox, counters);
+
+        Assert.assertTrue(result.isDone());
+        Assert.assertTrue(result.get().isSuccessful());
+
+        final List<Meter> metercaptorAllValues = meterCaptor.getAllValues();
+        Assert.assertEquals(2, metercaptorAllValues.size());
+        Assert.assertEquals(2L, metercaptorAllValues.get(0).getMeterId().getValue().longValue());
+        Assert.assertEquals(4L, metercaptorAllValues.get(1).getMeterId().getValue().longValue());
+
+        final InOrder inOrderMeter = Mockito.inOrder(flowCapableTxService, meterCommitter);
+        inOrderMeter.verify(meterCommitter, Mockito.times(2)).remove(Matchers.<InstanceIdentifier<Meter>>any(),
+                Matchers.<Meter>any(), Matchers.eq(NODE_IDENT));
+        //TODO: uncomment when enabled in impl
+//        inOrderMeter.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+        inOrderMeter.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testAddMissingGroups() throws Exception {
+        Mockito.when(groupCommitter.add(Matchers.<InstanceIdentifier<Group>>any(), groupCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new AddGroupOutputBuilder().build()).buildFuture());
+
+        ItemSyncBox<Group> groupBox1 = new ItemSyncBox<>();
+        groupBox1.getItemsToPush().add(createGroup(2L));
+
+        ItemSyncBox<Group> groupBox2 = new ItemSyncBox<>();
+        groupBox2.getItemsToPush().add(createGroupWithPreconditions(3L, 2L));
+        groupBox2.getItemsToPush().add(createGroupWithPreconditions(4L, 2L));
+
+        ItemSyncBox<Group> groupBox3 = new ItemSyncBox<>();
+        groupBox3.getItemsToPush().add(createGroupWithPreconditions(5L, 3L, 4L));
+
+        final List<ItemSyncBox<Group>> groupBoxLot = Lists.newArrayList(groupBox1, groupBox2, groupBox3);
+
+        final ListenableFuture<RpcResult<Void>> result = syncPlanPushStrategy.addMissingGroups(
+                NODE_ID, NODE_IDENT, groupBoxLot, counters);
+
+        Assert.assertTrue(result.isDone());
+        Assert.assertTrue(result.get().isSuccessful());
+
+        final List<Group> groupCaptorAllValues = groupCaptor.getAllValues();
+        Assert.assertEquals(4, groupCaptorAllValues.size());
+        Assert.assertEquals(2L, groupCaptorAllValues.get(0).getGroupId().getValue().longValue());
+        Assert.assertEquals(3L, groupCaptorAllValues.get(1).getGroupId().getValue().longValue());
+        Assert.assertEquals(4L, groupCaptorAllValues.get(2).getGroupId().getValue().longValue());
+        Assert.assertEquals(5L, groupCaptorAllValues.get(3).getGroupId().getValue().longValue());
+
+        final InOrder inOrderGroups = Mockito.inOrder(flowCapableTxService, groupCommitter);
+        // add 2
+        inOrderGroups.verify(groupCommitter).add(Matchers.<InstanceIdentifier<Group>>any(),
+                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
+        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+        // add 3, 4
+        inOrderGroups.verify(groupCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Group>>any(),
+                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
+        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+        // add 5
+        inOrderGroups.verify(groupCommitter).add(Matchers.<InstanceIdentifier<Group>>any(),
+                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
+        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+
+        inOrderGroups.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testAddMissingGroups_withUpdate() throws Exception {
+        Mockito.when(groupCommitter.add(Matchers.<InstanceIdentifier<Group>>any(), groupCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new AddGroupOutputBuilder().build()).buildFuture());
+
+        Mockito.when(groupCommitter.update(Matchers.<InstanceIdentifier<Group>>any(),
+                groupUpdateCaptor.capture(), groupUpdateCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new UpdateGroupOutputBuilder().build()).buildFuture());
+
+        ItemSyncBox<Group> groupBox1 = new ItemSyncBox<>();
+        groupBox1.getItemsToPush().add(createGroup(2L));
+        groupBox1.getItemsToUpdate().add(new ItemSyncBox.ItemUpdateTuple<>(
+                createGroup(1L), createGroupWithAction(1L)));
+
+        ItemSyncBox<Group> groupBox2 = new ItemSyncBox<>();
+        groupBox2.getItemsToPush().add(createGroupWithPreconditions(3L, 2L));
+        groupBox2.getItemsToPush().add(createGroupWithPreconditions(4L, 2L));
+
+        ItemSyncBox<Group> groupBox3 = new ItemSyncBox<>();
+        groupBox3.getItemsToPush().add(createGroupWithPreconditions(5L, 3L, 4L));
+
+        final List<ItemSyncBox<Group>> groupBoxLot = Lists.newArrayList(groupBox1, groupBox2, groupBox3);
+        final ListenableFuture<RpcResult<Void>> result = syncPlanPushStrategy.addMissingGroups(
+                NODE_ID, NODE_IDENT, groupBoxLot, counters);
+
+        Assert.assertTrue(result.isDone());
+        Assert.assertTrue(result.get().isSuccessful());
+
+        final List<Group> groupCaptorAllValues = groupCaptor.getAllValues();
+        Assert.assertEquals(4, groupCaptorAllValues.size());
+        Assert.assertEquals(2L, groupCaptorAllValues.get(0).getGroupId().getValue().longValue());
+        Assert.assertEquals(3L, groupCaptorAllValues.get(1).getGroupId().getValue().longValue());
+        Assert.assertEquals(4L, groupCaptorAllValues.get(2).getGroupId().getValue().longValue());
+        Assert.assertEquals(5L, groupCaptorAllValues.get(3).getGroupId().getValue().longValue());
+
+        final List<Group> groupUpdateCaptorAllValues = groupUpdateCaptor.getAllValues();
+        Assert.assertEquals(2, groupUpdateCaptorAllValues.size());
+        Assert.assertEquals(1L, groupUpdateCaptorAllValues.get(0).getGroupId().getValue().longValue());
+        Assert.assertEquals(1L, groupUpdateCaptorAllValues.get(1).getGroupId().getValue().longValue());
+
+        final InOrder inOrderGroups = Mockito.inOrder(flowCapableTxService, groupCommitter);
+
+        // add 2, update 1
+        inOrderGroups.verify(groupCommitter).add(Matchers.<InstanceIdentifier<Group>>any(),
+                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
+        inOrderGroups.verify(groupCommitter).update(Matchers.<InstanceIdentifier<Group>>any(),
+                Matchers.<Group>any(), Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
+        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+
+        // add 3, 4
+        inOrderGroups.verify(groupCommitter, Mockito.times(2)).add(Matchers.<InstanceIdentifier<Group>>any(),
+                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
+        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+        // add 5
+        inOrderGroups.verify(groupCommitter).add(Matchers.<InstanceIdentifier<Group>>any(),
+                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
+        inOrderGroups.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+
+        inOrderGroups.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testRemoveRedundantGroups() throws Exception {
+        Mockito.when(groupCommitter.remove(Matchers.<InstanceIdentifier<Group>>any(), groupCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new RemoveGroupOutputBuilder().build()).buildFuture());
+
+        ItemSyncBox<Group> groupBox1 = new ItemSyncBox<>();
+        groupBox1.getItemsToPush().add(createGroup(2L));
+        groupBox1.getItemsToUpdate().add(new ItemSyncBox.ItemUpdateTuple<>(
+                createGroup(1L), createGroupWithAction(1L)));
+
+        ItemSyncBox<Group> groupBox2 = new ItemSyncBox<>();
+        groupBox2.getItemsToPush().add(createGroupWithPreconditions(3L, 2L));
+        groupBox2.getItemsToPush().add(createGroupWithPreconditions(4L, 2L));
+
+        ItemSyncBox<Group> groupBox3 = new ItemSyncBox<>();
+        groupBox3.getItemsToPush().add(createGroupWithPreconditions(5L, 3L, 4L));
+
+        final List<ItemSyncBox<Group>> groupBoxLot = Lists.newArrayList(groupBox1, groupBox2, groupBox3);
+        final ListenableFuture<RpcResult<Void>> result = syncPlanPushStrategy.removeRedundantGroups(
+                NODE_ID, NODE_IDENT, groupBoxLot, counters);
+
+        Assert.assertTrue(result.isDone());
+        Assert.assertTrue(result.get().isSuccessful());
+
+        final List<Group> groupCaptorAllValues = groupCaptor.getAllValues();
+        Assert.assertEquals(4, groupCaptorAllValues.size());
+        Assert.assertEquals(5L, groupCaptorAllValues.get(0).getGroupId().getValue().longValue());
+        Assert.assertEquals(3L, groupCaptorAllValues.get(1).getGroupId().getValue().longValue());
+        Assert.assertEquals(4L, groupCaptorAllValues.get(2).getGroupId().getValue().longValue());
+        Assert.assertEquals(2L, groupCaptorAllValues.get(3).getGroupId().getValue().longValue());
+
+        final InOrder inOrderGroup = Mockito.inOrder(flowCapableTxService, groupCommitter);
+        // remove 5
+        inOrderGroup.verify(groupCommitter).remove(Matchers.<InstanceIdentifier<Group>>any(),
+                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
+        inOrderGroup.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+        // remove 3, 4
+        inOrderGroup.verify(groupCommitter, Mockito.times(2)).remove(Matchers.<InstanceIdentifier<Group>>any(),
+                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
+        inOrderGroup.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+        // remove 2
+        inOrderGroup.verify(groupCommitter).remove(Matchers.<InstanceIdentifier<Group>>any(),
+                Matchers.<Group>any(), Matchers.eq(NODE_IDENT));
+        inOrderGroup.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+
+        inOrderGroup.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testUpdateTableFeatures() throws Exception {
+        Mockito.when(tableCommitter.update(Matchers.<InstanceIdentifier<TableFeatures>>any(),
+                Matchers.isNull(TableFeatures.class), tableFeaturesCaptor.capture(),
+                Matchers.same(NODE_IDENT)))
+                .thenReturn(RpcResultBuilder.success(new UpdateTableOutputBuilder().build()).buildFuture());
+
+        final FlowCapableNode operational = new FlowCapableNodeBuilder()
+                .setTable(Collections.singletonList(new TableBuilder()
+                        .setId((short) 1)
+                        .build()))
+                .setTableFeatures(Collections.singletonList(new TableFeaturesBuilder()
+                        .setName("test table features")
+                        .setTableId((short) 1)
+                        .build()))
+                .build();
+
+        final ListenableFuture<RpcResult<Void>> result = syncPlanPushStrategy.updateTableFeatures(
+                NODE_IDENT, operational);
+
+        Assert.assertTrue(result.isDone());
+        Assert.assertTrue(result.get().isSuccessful());
+
+        final List<TableFeatures> groupCaptorAllValues = tableFeaturesCaptor.getAllValues();
+        //TODO: uncomment when enabled in impl
+//        Assert.assertEquals(1, groupCaptorAllValues.size());
+//        Assert.assertEquals("test table features", groupCaptorAllValues.get(0).getName());
+//        Assert.assertEquals(1, groupCaptorAllValues.get(0).getTableId().intValue());
+
+        Mockito.verify(flowCapableTxService).sendBarrier(Matchers.<SendBarrierInput>any());
+    }
+}
\ No newline at end of file
index 1057973d05fb32c8d50d4bbed0e6fee1d37db2fc..ac474a3397964178f8cd23895d0f86b6a71888f1 100644 (file)
@@ -8,15 +8,9 @@
 
 package org.opendaylight.openflowplugin.applications.frsync.util;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
@@ -51,9 +45,14 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
 
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.SettableFuture;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Test for {@link ReconcileUtil}.
@@ -100,7 +99,7 @@ public class ReconcileUtilTest {
      * @throws Exception
      */
     @Test
-    public void testResolveAndDivideGroups1() throws Exception {
+    public void testResolveAndDivideGroupDiffs1() throws Exception {
         final Map<Long, Group> installedGroups = new HashMap<>();
         installedGroups.put(1L, createGroup(1L));
         installedGroups.put(2L, createGroup(2L));
@@ -111,13 +110,13 @@ public class ReconcileUtilTest {
         pendingGroups.add(createGroup(3L));
         pendingGroups.add(createGroup(4L));
 
-        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroupDiffs(
                 NODE_ID, installedGroups, pendingGroups);
 
         Assert.assertEquals(1, plan.size());
 
-        Assert.assertEquals(1, plan.get(0).getItemsToAdd().size());
-        Assert.assertEquals(4L, plan.get(0).getItemsToAdd().iterator().next().getKey().getGroupId().getValue().longValue());
+        Assert.assertEquals(1, plan.get(0).getItemsToPush().size());
+        Assert.assertEquals(4L, plan.get(0).getItemsToPush().iterator().next().getKey().getGroupId().getValue().longValue());
         Assert.assertEquals(0, plan.get(0).getItemsToUpdate().size());
     }
 
@@ -127,7 +126,7 @@ public class ReconcileUtilTest {
      * @throws Exception
      */
     @Test
-    public void testResolveAndDivideGroups2() throws Exception {
+    public void testResolveAndDivideGroupDiffs2() throws Exception {
         final Map<Long, Group> installedGroups = new HashMap<>();
         installedGroups.put(1L, createGroup(1L));
 
@@ -136,21 +135,21 @@ public class ReconcileUtilTest {
         pendingGroups.add(createGroupWithPreconditions(3L, 2L, 4L));
         pendingGroups.add(createGroupWithPreconditions(4L, 2L));
 
-        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroupDiffs(
                 NODE_ID, installedGroups, pendingGroups);
 
         Assert.assertEquals(3, plan.size());
 
-        Assert.assertEquals(1, plan.get(0).getItemsToAdd().size());
-        Assert.assertEquals(2L, plan.get(0).getItemsToAdd().iterator().next().getKey().getGroupId().getValue().longValue());
+        Assert.assertEquals(1, plan.get(0).getItemsToPush().size());
+        Assert.assertEquals(2L, plan.get(0).getItemsToPush().iterator().next().getKey().getGroupId().getValue().longValue());
         Assert.assertEquals(0, plan.get(0).getItemsToUpdate().size());
 
-        Assert.assertEquals(1, plan.get(1).getItemsToAdd().size());
-        Assert.assertEquals(4L, plan.get(1).getItemsToAdd().iterator().next().getKey().getGroupId().getValue().longValue());
+        Assert.assertEquals(1, plan.get(1).getItemsToPush().size());
+        Assert.assertEquals(4L, plan.get(1).getItemsToPush().iterator().next().getKey().getGroupId().getValue().longValue());
         Assert.assertEquals(0, plan.get(1).getItemsToUpdate().size());
 
-        Assert.assertEquals(1, plan.get(2).getItemsToAdd().size());
-        Assert.assertEquals(3L, plan.get(2).getItemsToAdd().iterator().next().getKey().getGroupId().getValue().longValue());
+        Assert.assertEquals(1, plan.get(2).getItemsToPush().size());
+        Assert.assertEquals(3L, plan.get(2).getItemsToPush().iterator().next().getKey().getGroupId().getValue().longValue());
         Assert.assertEquals(0, plan.get(2).getItemsToUpdate().size());
     }
 
@@ -160,7 +159,7 @@ public class ReconcileUtilTest {
      * @throws Exception
      */
     @Test
-    public void testResolveAndDivideGroups3() throws Exception {
+    public void testResolveAndDivideGroupDiffs3() throws Exception {
         final Map<Long, Group> installedGroups = new HashMap<>();
         installedGroups.put(1L, createGroup(1L));
         installedGroups.put(2L, createGroupWithPreconditions(2L, 1L));
@@ -169,7 +168,7 @@ public class ReconcileUtilTest {
         pendingGroups.add(createGroup(1L));
         pendingGroups.add(createGroupWithPreconditions(2L, 1L));
 
-        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroupDiffs(
                 NODE_ID, installedGroups, pendingGroups);
 
         Assert.assertEquals(0, plan.size());
@@ -181,7 +180,7 @@ public class ReconcileUtilTest {
      * @throws Exception
      */
     @Test
-    public void testResolveAndDivideGroups4() throws Exception {
+    public void testResolveAndDivideGroupDiffs4() throws Exception {
         final Map<Long, Group> installedGroups = new HashMap<>();
         installedGroups.put(1L, createGroup(1L));
         installedGroups.put(2L, createGroup(2L));
@@ -190,11 +189,11 @@ public class ReconcileUtilTest {
         pendingGroups.add(createGroupWithPreconditions(1L, 2L));
         pendingGroups.add(createGroup(2L));
 
-        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroupDiffs(
                 NODE_ID, installedGroups, pendingGroups);
 
         Assert.assertEquals(1, plan.size());
-        Assert.assertEquals(0, plan.get(0).getItemsToAdd().size());
+        Assert.assertEquals(0, plan.get(0).getItemsToPush().size());
         Assert.assertEquals(1, plan.get(0).getItemsToUpdate().size());
         final ItemSyncBox.ItemUpdateTuple<Group> firstItemUpdateTuple = plan.get(0).getItemsToUpdate().iterator().next();
         Assert.assertEquals(1L, firstItemUpdateTuple.getOriginal().getGroupId().getValue().longValue());
@@ -207,7 +206,7 @@ public class ReconcileUtilTest {
      * @throws Exception
      */
     @Test
-    public void testResolveAndDivideGroups5() throws Exception {
+    public void testResolveAndDivideGroupDiffs5() throws Exception {
         final Map<Long, Group> installedGroups = new HashMap<>();
         installedGroups.put(1L, createGroup(1L));
         installedGroups.put(2L, createGroup(2L));
@@ -216,7 +215,7 @@ public class ReconcileUtilTest {
         pendingGroups.add(createGroupWithPreconditions(1L, 2L));
         pendingGroups.add(createGroup(2L));
 
-        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroupDiffs(
                 NODE_ID, installedGroups, pendingGroups, false);
 
         Assert.assertEquals(0, plan.size());
@@ -228,7 +227,7 @@ public class ReconcileUtilTest {
      * @throws Exception
      */
     @Test
-    public void testResolveAndDivideGroups_negative1() throws Exception {
+    public void testResolveAndDivideGroupDiffs_negative1() throws Exception {
         final Map<Long, Group> installedGroups = new HashMap<>();
         installedGroups.put(1L, createGroup(1L));
         installedGroups.put(2L, createGroup(2L));
@@ -237,7 +236,7 @@ public class ReconcileUtilTest {
         pendingGroups.add(createGroupWithPreconditions(3L, 4L));
 
         thrown.expect(IllegalStateException.class);
-        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroupDiffs(
                 NODE_ID, installedGroups, pendingGroups);
     }
 
@@ -247,7 +246,7 @@ public class ReconcileUtilTest {
      * @throws Exception
      */
     @Test
-    public void testResolveAndDivideGroups_negative2() throws Exception {
+    public void testResolveAndDivideGroupDiffs_negative2() throws Exception {
         final Map<Long, Group> installedGroups = new HashMap<>();
         installedGroups.put(1L, createGroup(1L));
         installedGroups.put(2L, createGroup(2L));
@@ -256,7 +255,7 @@ public class ReconcileUtilTest {
         pendingGroups.add(createGroupWithPreconditions(1L, 3L));
 
         thrown.expect(IllegalStateException.class);
-        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroupDiffs(
                 NODE_ID, installedGroups, pendingGroups);
     }
 
@@ -311,4 +310,43 @@ public class ReconcileUtilTest {
                 .setBuckets(buckets)
                 .build();
     }
+
+    /**
+     * covers {@link ReconcileUtil#countTotalUpdated(List)} too
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testCountTotalAdds() throws Exception {
+        List<ItemSyncBox<String>> syncPlan = new ArrayList<>();
+        ItemSyncBox<String> syncBox1 = createSyncBox("a,b", "x,y,z");
+        syncPlan.add(syncBox1);
+        syncPlan.add(syncBox1);
+        Assert.assertEquals(4, ReconcileUtil.countTotalAdds(syncPlan));
+        Assert.assertEquals(6, ReconcileUtil.countTotalUpdated(syncPlan));
+    }
+
+    private ItemSyncBox<String> createSyncBox(final String pushes, final String updates) {
+        ItemSyncBox<String> syncBox1 = new ItemSyncBox<>();
+        syncBox1.getItemsToPush().addAll(Arrays.asList(pushes.split(",")));
+        for (String orig : updates.split(",")) {
+            syncBox1.getItemsToUpdate().add(new ItemSyncBox.ItemUpdateTuple<>(orig, orig + "_updated"));
+        }
+        return syncBox1;
+    }
+
+    @Test
+    public void testResolveMeterDiffs() throws Exception {
+
+    }
+
+    @Test
+    public void testResolveFlowDiffsInTable() throws Exception {
+
+    }
+
+    @Test
+    public void testResolveFlowDiffsInAllTables() throws Exception {
+
+    }
 }
\ No newline at end of file