Bug 5575 added JUnit tests 54/38154/8
authorAndrej Leitner <anleitne@cisco.com>
Wed, 27 Apr 2016 08:00:41 +0000 (10:00 +0200)
committerAndrej Leitner <anleitne@cisco.com>
Tue, 24 May 2016 06:32:06 +0000 (08:32 +0200)
  - basic tests for forwarders, listeners and syncreactor

Change-Id: I11fb3c9ca4bd049a170ce6e04ff754399cdeb7ef
Signed-off-by: Andrej Leitner <anleitne@cisco.com>
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/FlowForwarderTest.java [new file with mode: 0644]
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/ForwardingRulesSyncProviderTest.java [new file with mode: 0644]
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/GroupForwarderTest.java [new file with mode: 0644]
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/MeterForwarderTest.java [new file with mode: 0644]
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/SimplifiedConfigListenerTest.java [new file with mode: 0644]
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/SimplifiedOperationalListenerTest.java [new file with mode: 0644]
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/SyncReactorImplTest.java [new file with mode: 0644]
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/TableForwarderTest.java [new file with mode: 0644]
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/util/ReconcileUtilTest.java [new file with mode: 0644]
applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/util/SemaphoreKeeperTest.java [new file with mode: 0644]
applications/forwardingrules-sync/src/test/resources/log4j.xml [new file with mode: 0644]

diff --git a/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/FlowForwarderTest.java b/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/FlowForwarderTest.java
new file mode 100644 (file)
index 0000000..1effafd
--- /dev/null
@@ -0,0 +1,207 @@
+/**
+ * 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;
+
+import java.math.BigInteger;
+import java.util.Collections;
+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.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.DropActionCaseBuilder;
+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.FlowId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
+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.AddFlowInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.OriginalFlow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.UpdatedFlow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.TransactionId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.Instructions;
+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.Match;
+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.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.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+
+/**
+ * Test for {@link FlowForwarder}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class FlowForwarderTest {
+    private final NodeKey s1Key = new NodeKey(new NodeId("S1"));
+    private final TableKey tableKey = new TableKey((short) 2);
+    private final FlowId flowId = new FlowId("test_Flow");
+    private final FlowKey flowKey = new FlowKey(flowId);
+    private final Match emptyMatch = new MatchBuilder().build();
+    private final Flow flow = new FlowBuilder()
+            .setId(flowId)
+            .setTableId((short) 2)
+            .setMatch(emptyMatch)
+            .build();
+
+    private final KeyedInstanceIdentifier<Node, NodeKey> nodePath = InstanceIdentifier.create(Nodes.class)
+            .child(Node.class, s1Key);
+    private final InstanceIdentifier<FlowCapableNode> flowCapableNodePath = nodePath
+            .augmentation(FlowCapableNode.class);
+    private final InstanceIdentifier<Table> tableII = flowCapableNodePath.child(Table.class, tableKey);
+    private final InstanceIdentifier<Flow> flowPath = tableII.child(Flow.class, flowKey);
+
+    private FlowForwarder flowForwarder;
+
+    @Mock
+    private SalFlowService salFlowService;
+    @Captor
+    private ArgumentCaptor<AddFlowInput> addFlowInputCpt;
+    @Captor
+    private ArgumentCaptor<UpdateFlowInput> updateFlowInputCpt;
+    @Captor
+    private ArgumentCaptor<RemoveFlowInput> removeFlowInputCpt;
+
+
+    @Before
+    public void setUp() throws Exception {
+        flowForwarder = new FlowForwarder(salFlowService);
+    }
+
+    @Test
+    public void addTest() throws Exception {
+        Mockito.when(salFlowService.addFlow(addFlowInputCpt.capture())).thenReturn(
+                RpcResultBuilder.success(
+                        new AddFlowOutputBuilder()
+                                .setTransactionId(new TransactionId(BigInteger.ONE))
+                                .build()).buildFuture());
+
+        final Future<RpcResult<AddFlowOutput>> addResult = flowForwarder.add(flowPath, flow, flowCapableNodePath);
+
+        Mockito.verify(salFlowService).addFlow(Matchers.<AddFlowInput>any());
+        final AddFlowInput flowInput = addFlowInputCpt.getValue();
+        Assert.assertEquals(2, flowInput.getTableId().shortValue());
+        Assert.assertEquals(emptyMatch, flowInput.getMatch());
+        Assert.assertEquals(null, flowInput.getInstructions());
+        Assert.assertEquals(nodePath, flowInput.getNode().getValue());
+        Assert.assertEquals(flowPath, flowInput.getFlowRef().getValue());
+        Assert.assertEquals(null, flowInput.isStrict());
+
+
+        final RpcResult<AddFlowOutput> addFlowOutputRpcResult = addResult.get(2, TimeUnit.SECONDS);
+        Assert.assertTrue(addFlowOutputRpcResult.isSuccessful());
+        final AddFlowOutput resultValue = addFlowOutputRpcResult.getResult();
+        Assert.assertEquals(1, resultValue.getTransactionId().getValue().intValue());
+    }
+
+    @Test
+    public void updateTest() throws Exception {
+        Mockito.when(salFlowService.updateFlow(updateFlowInputCpt.capture())).thenReturn(
+                RpcResultBuilder.success(
+                        new UpdateFlowOutputBuilder()
+                                .setTransactionId(new TransactionId(BigInteger.ONE))
+                                .build()).buildFuture());
+
+        final Instructions originalInstructions = new InstructionsBuilder()
+                .setInstruction(Collections.singletonList(new InstructionBuilder()
+                        .setInstruction(new ApplyActionsCaseBuilder()
+                                .setApplyActions(new ApplyActionsBuilder()
+                                        .setAction(Collections.singletonList(new ActionBuilder()
+                                                .setAction(new DropActionCaseBuilder()
+                                                        .build())
+                                                .build())
+                                        ).build()
+                                ).build())
+                        .build())
+                ).build();
+
+        final Flow flowUpdated = new FlowBuilder(flow)
+                .setInstructions(originalInstructions)
+                .setMatch(new MatchBuilder().build())
+                .build();
+
+        final Future<RpcResult<UpdateFlowOutput>> updateResult = flowForwarder.update(flowPath, flow, flowUpdated, flowCapableNodePath);
+
+        Mockito.verify(salFlowService).updateFlow(Matchers.<UpdateFlowInput>any());
+        final UpdateFlowInput updateFlowInput = updateFlowInputCpt.getValue();
+        final OriginalFlow flowOrigInput = updateFlowInput.getOriginalFlow();
+        final UpdatedFlow flowInput = updateFlowInput.getUpdatedFlow();
+
+        Assert.assertEquals(nodePath, updateFlowInput.getNode().getValue());
+        Assert.assertEquals(flowPath, updateFlowInput.getFlowRef().getValue());
+
+        Assert.assertEquals(2, flowInput.getTableId().shortValue());
+        Assert.assertEquals(emptyMatch, flowInput.getMatch());
+        Assert.assertEquals(originalInstructions, flowInput.getInstructions());
+        Assert.assertEquals(true, flowInput.isStrict());
+
+        Assert.assertEquals(2, flowOrigInput.getTableId().shortValue());
+        Assert.assertEquals(emptyMatch, flowOrigInput.getMatch());
+        Assert.assertEquals(null, flowOrigInput.getInstructions());
+        Assert.assertEquals(true, flowOrigInput.isStrict());
+
+
+        final RpcResult<UpdateFlowOutput> updateFlowOutputRpcResult = updateResult.get(2, TimeUnit.SECONDS);
+        Assert.assertTrue(updateFlowOutputRpcResult.isSuccessful());
+        final UpdateFlowOutput resultValue = updateFlowOutputRpcResult.getResult();
+        Assert.assertEquals(1, resultValue.getTransactionId().getValue().intValue());
+    }
+
+    @Test
+    public void removeTest() throws Exception {
+        Mockito.when(salFlowService.removeFlow(removeFlowInputCpt.capture())).thenReturn(
+                RpcResultBuilder.success(
+                        new RemoveFlowOutputBuilder()
+                                .setTransactionId(new TransactionId(BigInteger.ONE))
+                                .build()).buildFuture());
+
+        final Flow removeFlow = new FlowBuilder(flow).build();
+        final Future<RpcResult<RemoveFlowOutput>> removeResult = flowForwarder.remove(flowPath, removeFlow, flowCapableNodePath);
+
+        Mockito.verify(salFlowService).removeFlow(Matchers.<RemoveFlowInput>any());
+        final RemoveFlowInput flowInput = removeFlowInputCpt.getValue();
+        Assert.assertEquals(2, flowInput.getTableId().shortValue());
+        Assert.assertEquals(emptyMatch, flowInput.getMatch());
+        Assert.assertEquals(null, flowInput.getInstructions());
+        Assert.assertEquals(true, flowInput.isStrict());
+
+
+        final RpcResult<RemoveFlowOutput> removeFlowOutputRpcResult = removeResult.get(2, TimeUnit.SECONDS);
+        Assert.assertTrue(removeFlowOutputRpcResult.isSuccessful());
+        final RemoveFlowOutput resultValue = removeFlowOutputRpcResult.getResult();
+        Assert.assertEquals(1, resultValue.getTransactionId().getValue().intValue());
+    }
+}
diff --git a/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/ForwardingRulesSyncProviderTest.java b/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/ForwardingRulesSyncProviderTest.java
new file mode 100644 (file)
index 0000000..b994633
--- /dev/null
@@ -0,0 +1,79 @@
+/**
+ * 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;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+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.controller.md.sal.binding.api.DataTreeChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
+import org.opendaylight.controller.sal.binding.api.RpcConsumerRegistry;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.FlowCapableTransactionService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.SalGroupService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.SalMeterService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.service.rev131026.SalTableService;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+
+/**
+ * Test for {@link ForwardingRulesSyncProvider}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ForwardingRulesSyncProviderTest {
+
+    private ForwardingRulesSyncProvider provider;
+    @Mock
+    private DataBroker dataBroker;
+    @Mock
+    private RpcConsumerRegistry rpcRegistry;
+    @Mock
+    private BindingAwareBroker broker;
+    @Mock
+    private BindingAwareBroker.ProviderContext providerContext;
+
+    @Before
+    public void setUp() throws Exception {
+        Mockito.when(rpcRegistry.getRpcService(Matchers.<Class<? extends RpcService>>any()))
+                .thenAnswer(new Answer<RpcService>() {
+                    @Override
+                    public RpcService answer(final InvocationOnMock invocation) throws Throwable {
+                        Class<? extends RpcService> serviceType = (Class<? extends RpcService>) invocation.getArguments()[0];
+                        return Mockito.mock(serviceType);
+                    }
+                });
+
+        provider = new ForwardingRulesSyncProvider(broker, dataBroker, rpcRegistry);
+
+        Mockito.verify(rpcRegistry).getRpcService(SalFlowService.class);
+        Mockito.verify(rpcRegistry).getRpcService(SalGroupService.class);
+        Mockito.verify(rpcRegistry).getRpcService(SalMeterService.class);
+        Mockito.verify(rpcRegistry).getRpcService(SalTableService.class);
+        Mockito.verify(rpcRegistry).getRpcService(FlowCapableTransactionService.class);
+
+        Mockito.verify(broker).registerProvider(provider);
+    }
+
+    @Test
+    public void testOnSessionInitiated() throws Exception {
+        provider.onSessionInitiated(providerContext);
+
+        Mockito.verify(dataBroker, Mockito.times(2)).registerDataTreeChangeListener(
+                Matchers.<DataTreeIdentifier<FlowCapableNode>>any(),
+                Matchers.<DataTreeChangeListener<FlowCapableNode>>any());
+    }
+}
\ No newline at end of file
diff --git a/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/GroupForwarderTest.java b/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/GroupForwarderTest.java
new file mode 100644 (file)
index 0000000..2cf2eec
--- /dev/null
@@ -0,0 +1,180 @@
+/**
+ * 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;
+
+import java.math.BigInteger;
+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.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.TransactionId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.AddGroupInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.AddGroupOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.AddGroupOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.RemoveGroupInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.RemoveGroupOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.RemoveGroupOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.SalGroupService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.UpdateGroupInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.group.service.rev130918.UpdateGroupOutput;
+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.BucketsBuilder;
+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.group.types.rev131018.groups.GroupKey;
+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.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+
+/**
+ * Test for {@link GroupForwarder}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class GroupForwarderTest {
+
+    private final NodeKey s1Key = new NodeKey(new NodeId("S1"));
+    private final GroupId groupId = new GroupId(42L);
+    private final GroupKey groupKey = new GroupKey(groupId);
+    private final Group group = new GroupBuilder()
+            .setGroupId(groupId)
+            .setGroupName("test-group")
+            .setBuckets(new BucketsBuilder().build())
+            .build();
+
+    private final KeyedInstanceIdentifier<Node, NodeKey> nodePath = InstanceIdentifier.create(Nodes.class)
+            .child(Node.class, s1Key);
+    private final InstanceIdentifier<FlowCapableNode> flowCapableNodePath = nodePath
+            .augmentation(FlowCapableNode.class);
+    private final InstanceIdentifier<Group> groupPath = flowCapableNodePath.child(Group.class, groupKey);
+
+    @Mock
+    private SalGroupService salGroupService;
+    @Captor
+    private ArgumentCaptor<AddGroupInput> addGroupInputCpt;
+    @Captor
+    private ArgumentCaptor<RemoveGroupInput> removeGroupInputCpt;
+    @Captor
+    private ArgumentCaptor<UpdateGroupInput> updateGroupInputCpt;
+
+    private TransactionId txId;
+
+    private GroupForwarder groupForwarder;
+
+    @Before
+    public void setUp() throws Exception {
+        groupForwarder = new GroupForwarder(salGroupService);
+        txId = new TransactionId(BigInteger.ONE);
+    }
+
+    @Test
+    public void testRemove() throws Exception {
+        Mockito.when(salGroupService.removeGroup(removeGroupInputCpt.capture())).thenReturn(
+                RpcResultBuilder.success(
+                        new RemoveGroupOutputBuilder()
+                                .setTransactionId(txId)
+                                .build())
+                        .buildFuture()
+        );
+
+        final Future<RpcResult<RemoveGroupOutput>> addResult = groupForwarder.remove(groupPath, group, flowCapableNodePath);
+
+        Mockito.verify(salGroupService).removeGroup(Matchers.<RemoveGroupInput>any());
+
+        Assert.assertTrue(addResult.isDone());
+        final RpcResult<RemoveGroupOutput> result = addResult.get(2, TimeUnit.SECONDS);
+        Assert.assertTrue(result.isSuccessful());
+
+        Assert.assertEquals(1, result.getResult().getTransactionId().getValue().intValue());
+
+        final RemoveGroupInput removeGroupInput = removeGroupInputCpt.getValue();
+        Assert.assertEquals(groupPath, removeGroupInput.getGroupRef().getValue());
+        Assert.assertNull(removeGroupInput.getBuckets());
+        Assert.assertEquals(nodePath, removeGroupInput.getNode().getValue());
+        Assert.assertEquals("test-group", removeGroupInput.getGroupName());
+    }
+
+    @Test
+    public void testUpdate() throws Exception {
+        Mockito.when(salGroupService.updateGroup(updateGroupInputCpt.capture())).thenReturn(
+                RpcResultBuilder.success(
+                        new UpdateGroupOutputBuilder()
+                                .setTransactionId(txId)
+                                .build())
+                        .buildFuture()
+        );
+
+        Group groupOriginal = new GroupBuilder(group).build();
+        Group groupUpdate = new GroupBuilder(group)
+                .setGroupName("another-test")
+                .build();
+
+        final Future<RpcResult<UpdateGroupOutput>> addResult = groupForwarder.update(groupPath, groupOriginal, groupUpdate,
+                flowCapableNodePath);
+
+        Mockito.verify(salGroupService).updateGroup(Matchers.<UpdateGroupInput>any());
+
+        Assert.assertTrue(addResult.isDone());
+        final RpcResult<UpdateGroupOutput> result = addResult.get(2, TimeUnit.SECONDS);
+        Assert.assertTrue(result.isSuccessful());
+
+        Assert.assertEquals(1, result.getResult().getTransactionId().getValue().intValue());
+
+        final UpdateGroupInput updateGroupInput = updateGroupInputCpt.getValue();
+        Assert.assertEquals(groupPath, updateGroupInput.getGroupRef().getValue());
+        Assert.assertEquals(nodePath, updateGroupInput.getNode().getValue());
+        Assert.assertNotNull(updateGroupInput.getOriginalGroup().getBuckets());
+        Assert.assertNotNull(updateGroupInput.getUpdatedGroup().getBuckets());
+
+        Assert.assertEquals("test-group", updateGroupInput.getOriginalGroup().getGroupName());
+        Assert.assertEquals("another-test", updateGroupInput.getUpdatedGroup().getGroupName());
+    }
+
+    @Test
+    public void testAdd() throws Exception {
+        Mockito.when(salGroupService.addGroup(addGroupInputCpt.capture())).thenReturn(
+                RpcResultBuilder.success(
+                        new AddGroupOutputBuilder()
+                                .setTransactionId(txId)
+                                .build())
+                        .buildFuture()
+        );
+
+        final Future<RpcResult<AddGroupOutput>> addResult = groupForwarder.add(groupPath, group, flowCapableNodePath);
+
+        Mockito.verify(salGroupService).addGroup(Matchers.<AddGroupInput>any());
+
+        Assert.assertTrue(addResult.isDone());
+        final RpcResult<AddGroupOutput> result = addResult.get(2, TimeUnit.SECONDS);
+        Assert.assertTrue(result.isSuccessful());
+
+        Assert.assertEquals(1, result.getResult().getTransactionId().getValue().intValue());
+
+        final AddGroupInput addGroupInput = addGroupInputCpt.getValue();
+        Assert.assertEquals(groupPath, addGroupInput.getGroupRef().getValue());
+        Assert.assertEquals(nodePath, addGroupInput.getNode().getValue());
+        Assert.assertNotNull(addGroupInput.getBuckets());
+        Assert.assertEquals("test-group", addGroupInput.getGroupName());
+    }
+}
\ No newline at end of file
diff --git a/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/MeterForwarderTest.java b/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/MeterForwarderTest.java
new file mode 100644 (file)
index 0000000..7c82074
--- /dev/null
@@ -0,0 +1,167 @@
+/**
+ * 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;
+
+import java.math.BigInteger;
+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.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+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.MeterBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.MeterKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.TransactionId;
+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.AddMeterInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.AddMeterOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.AddMeterOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.RemoveMeterInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.RemoveMeterOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.RemoveMeterOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.SalMeterService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.UpdateMeterInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.UpdateMeterOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.service.rev130918.UpdateMeterOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.MeterId;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+
+/**
+ * Test for {@link MeterForwarder}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class MeterForwarderTest {
+
+    private final NodeKey s1Key = new NodeKey(new NodeId("S1"));
+    private final MeterId meterId = new MeterId(42L);
+    private final MeterKey meterKey = new MeterKey(meterId);
+    private final Meter meter = new MeterBuilder()
+            .setMeterId(meterId)
+            .setMeterName("test-meter")
+            .build();
+
+    private final KeyedInstanceIdentifier<Node, NodeKey> nodePath = InstanceIdentifier.create(Nodes.class)
+            .child(Node.class, s1Key);
+    private final InstanceIdentifier<FlowCapableNode> flowCapableNodePath = nodePath
+            .augmentation(FlowCapableNode.class);
+    private final InstanceIdentifier<Meter> meterPath = flowCapableNodePath.child(Meter.class, meterKey);
+
+    @Mock
+    private SalMeterService salMeterService;
+    @Captor
+    private ArgumentCaptor<AddMeterInput> addMeterInputCpt;
+    @Captor
+    private ArgumentCaptor<RemoveMeterInput> removeMeterInputCpt;
+    @Captor
+    private ArgumentCaptor<UpdateMeterInput> updateMeterInputCpt;
+
+    private TransactionId txId;
+
+    private MeterForwarder meterForwarder;
+
+    @Before
+    public void setUp() throws Exception {
+        meterForwarder = new MeterForwarder(salMeterService);
+        txId = new TransactionId(BigInteger.ONE);
+    }
+
+    @Test
+    public void testRemove() throws Exception {
+        Mockito.when(salMeterService.removeMeter(removeMeterInputCpt.capture())).thenReturn(
+                RpcResultBuilder.success(new RemoveMeterOutputBuilder()
+                        .setTransactionId(txId)
+                        .build()).buildFuture()
+        );
+
+        Meter removeMeter = new MeterBuilder(meter).build();
+
+        final Future<RpcResult<RemoveMeterOutput>> removeResult = meterForwarder.remove(meterPath, removeMeter, flowCapableNodePath);
+        Mockito.verify(salMeterService).removeMeter(Matchers.<RemoveMeterInput>any());
+
+        Assert.assertTrue(removeResult.isDone());
+        final RpcResult<RemoveMeterOutput> meterResult = removeResult.get(2, TimeUnit.SECONDS);
+        Assert.assertTrue(meterResult.isSuccessful());
+
+        Assert.assertEquals(1, meterResult.getResult().getTransactionId().getValue().intValue());
+
+        final RemoveMeterInput removeMeterInput = removeMeterInputCpt.getValue();
+        Assert.assertEquals(meterPath, removeMeterInput.getMeterRef().getValue());
+        Assert.assertEquals(nodePath, removeMeterInput.getNode().getValue());
+        Assert.assertEquals("test-meter", removeMeterInput.getMeterName());
+    }
+
+    @Test
+    public void testUpdate() throws Exception {
+        Mockito.when(salMeterService.updateMeter(updateMeterInputCpt.capture())).thenReturn(
+                RpcResultBuilder.success(new UpdateMeterOutputBuilder()
+                        .setTransactionId(txId)
+                        .build()).buildFuture()
+        );
+
+        Meter meterOriginal = new MeterBuilder(meter).build();
+        Meter meterUpdate = new MeterBuilder(meter)
+                .setMeterName("another-test")
+                .build();
+
+        final Future<RpcResult<UpdateMeterOutput>> updateResult = meterForwarder.update(meterPath, meterOriginal, meterUpdate,
+                flowCapableNodePath);
+        Mockito.verify(salMeterService).updateMeter(Matchers.<UpdateMeterInput>any());
+
+        Assert.assertTrue(updateResult.isDone());
+        final RpcResult<UpdateMeterOutput> meterResult = updateResult.get(2, TimeUnit.SECONDS);
+        Assert.assertTrue(meterResult.isSuccessful());
+
+        Assert.assertEquals(1, meterResult.getResult().getTransactionId().getValue().intValue());
+
+        final UpdateMeterInput updateMeterInput = updateMeterInputCpt.getValue();
+        Assert.assertEquals(meterPath, updateMeterInput.getMeterRef().getValue());
+        Assert.assertEquals(nodePath, updateMeterInput.getNode().getValue());
+
+        Assert.assertEquals("test-meter", updateMeterInput.getOriginalMeter().getMeterName());
+        Assert.assertEquals("another-test", updateMeterInput.getUpdatedMeter().getMeterName());
+    }
+
+    @Test
+    public void testAdd() throws Exception {
+        Mockito.when(salMeterService.addMeter(addMeterInputCpt.capture())).thenReturn(
+                RpcResultBuilder.success(new AddMeterOutputBuilder()
+                        .setTransactionId(txId)
+                        .build()).buildFuture()
+        );
+
+        final Future<RpcResult<AddMeterOutput>> addResult = meterForwarder.add(meterPath, meter, flowCapableNodePath);
+        Mockito.verify(salMeterService).addMeter(Matchers.<AddMeterInput>any());
+
+        Assert.assertTrue(addResult.isDone());
+        final RpcResult<AddMeterOutput> meterResult = addResult.get(2, TimeUnit.SECONDS);
+        Assert.assertTrue(meterResult.isSuccessful());
+
+        Assert.assertEquals(1, meterResult.getResult().getTransactionId().getValue().intValue());
+
+        final AddMeterInput addMeterInput = addMeterInputCpt.getValue();
+        Assert.assertEquals(meterPath, addMeterInput.getMeterRef().getValue());
+        Assert.assertEquals(nodePath, addMeterInput.getNode().getValue());
+        Assert.assertEquals("test-meter", addMeterInput.getMeterName());
+    }
+}
\ No newline at end of file
diff --git a/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/SimplifiedConfigListenerTest.java b/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/SimplifiedConfigListenerTest.java
new file mode 100644 (file)
index 0000000..3b5caea
--- /dev/null
@@ -0,0 +1,103 @@
+/**
+ * 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;
+
+import java.util.Collections;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
+import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+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.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.Futures;
+
+/**
+ * Test for {@link SimplifiedConfigListener}.
+ */
+@SuppressWarnings("deprecation")
+@RunWith(MockitoJUnitRunner.class)
+public class SimplifiedConfigListenerTest {
+
+    @Mock
+    private SyncReactor reactor;
+    @Mock
+    private DataBroker db;
+    @Mock
+    private DataTreeModification<FlowCapableNode> dataTreeModification;
+    @Mock
+    private ReadOnlyTransaction roTx;
+    @Mock
+    private DataObjectModification<FlowCapableNode> configModification;
+
+    private InstanceIdentifier<FlowCapableNode> nodePath;
+    private SimplifiedConfigListener nodeListenerConfig;
+
+    @Before
+    public void setUp() throws Exception {
+        final FlowCapableNodeSnapshotDao configSnaphot = new FlowCapableNodeSnapshotDao();
+        final FlowCapableNodeSnapshotDao operationalSnaphot = new FlowCapableNodeSnapshotDao();
+        final FlowCapableNodeDao operationalDao = new FlowCapableNodeCachedDao(operationalSnaphot,
+                new FlowCapableNodeOdlDao(db, LogicalDatastoreType.OPERATIONAL));
+
+        
+        nodeListenerConfig = new SimplifiedConfigListener(reactor, configSnaphot, operationalDao);
+        nodePath = InstanceIdentifier.create(Nodes.class)
+                .child(Node.class, new NodeKey(new NodeId("testNode")))
+                .augmentation(FlowCapableNode.class);
+    }
+
+    @Test
+    public void testDSLogicalType() throws Exception {
+        Assert.assertEquals(LogicalDatastoreType.CONFIGURATION, nodeListenerConfig.dsType());
+    }
+
+    @Test
+    public void testOnDataTreeChanged() throws Exception {
+        final FlowCapableNode configTree = Mockito.mock(FlowCapableNode.class);
+        final FlowCapableNode operationalTree = Mockito.mock(FlowCapableNode.class);
+        final DataTreeIdentifier<FlowCapableNode> dataTreeIdentifier =
+                new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, nodePath);
+
+        Mockito.when(dataTreeModification.getRootPath()).thenReturn(dataTreeIdentifier);
+        Mockito.when(dataTreeModification.getRootNode()).thenReturn(configModification);
+        Mockito.when(configModification.getDataAfter()).thenReturn(configTree);
+        Mockito.when(db.newReadOnlyTransaction()).thenReturn(roTx);
+        Mockito.doReturn(Futures.immediateCheckedFuture(Optional.of(operationalTree))).when(
+                roTx).read(LogicalDatastoreType.OPERATIONAL, nodePath);
+        Mockito.when(reactor.syncup(Matchers.<InstanceIdentifier<FlowCapableNode>>any(),Matchers.<FlowCapableNode>any(),Matchers.<FlowCapableNode>any()))
+                .thenReturn(Futures.immediateFuture(Boolean.TRUE));
+
+        nodeListenerConfig.onDataTreeChanged(Collections.singleton(dataTreeModification));
+
+        Mockito.verify(reactor).syncup(nodePath, configTree, operationalTree);
+        Mockito.verify(roTx).close();
+    }
+}
\ No newline at end of file
diff --git a/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/SimplifiedOperationalListenerTest.java b/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/SimplifiedOperationalListenerTest.java
new file mode 100644 (file)
index 0000000..c4683fa
--- /dev/null
@@ -0,0 +1,113 @@
+/**
+ * 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;
+
+import java.util.Collections;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
+import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+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.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+
+/**
+ * Test for {@link SimplifiedOperationalListener}.
+ */
+@SuppressWarnings("deprecation")
+@RunWith(MockitoJUnitRunner.class)
+public class SimplifiedOperationalListenerTest {
+
+    @Mock
+    private SyncReactor reactor;
+    @Mock
+    private DataBroker db;
+    @Mock
+    private DataTreeModification<Node> dataTreeModification;
+    @Mock
+    private ReadOnlyTransaction roTx;
+    @Mock
+    private DataObjectModification<Node> operationalModification;
+
+    private NodeId nodeId;
+    private InstanceIdentifier<Node> nodePath;
+    private InstanceIdentifier<FlowCapableNode> fcNodePath;
+    private SimplifiedOperationalListener nodeListenerOperational;
+
+    @SuppressWarnings("deprecation")
+    @Before
+    public void setUp() throws Exception {
+        final FlowCapableNodeSnapshotDao configSnaphot = new FlowCapableNodeSnapshotDao();
+        final FlowCapableNodeSnapshotDao operationalSnaphot = new FlowCapableNodeSnapshotDao();
+        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));
+        fcNodePath = nodePath.augmentation(FlowCapableNode.class);
+    }
+
+    @Test
+    public void testDSLogicalType() throws Exception {
+        Assert.assertEquals(LogicalDatastoreType.OPERATIONAL, nodeListenerOperational.dsType());
+    }
+
+    @Test
+    public void testOnDataTreeChanged() throws Exception {
+        final FlowCapableNode configTree = Mockito.mock(FlowCapableNode.class);
+        final Node mockOperationalNode = Mockito.mock(Node.class);
+        final FlowCapableNode mockOperationalFlowCapableNode = Mockito.mock(FlowCapableNode.class);
+        Mockito.when(mockOperationalNode.getAugmentation(FlowCapableNode.class))
+            .thenReturn(mockOperationalFlowCapableNode);
+        Mockito.when(mockOperationalNode.getId()).thenReturn(nodeId);
+        
+        final DataTreeIdentifier<Node> dataTreeIdentifier =
+                new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL, nodePath);
+
+        Mockito.when(dataTreeModification.getRootPath()).thenReturn(dataTreeIdentifier);
+        Mockito.when(dataTreeModification.getRootNode()).thenReturn(operationalModification);
+        Mockito.when(operationalModification.getDataAfter()).thenReturn(mockOperationalNode);
+        Mockito.when(db.newReadOnlyTransaction()).thenReturn(roTx);
+        Mockito.doReturn(Futures.immediateCheckedFuture(Optional.of(configTree))).when(
+                roTx).read(LogicalDatastoreType.CONFIGURATION, fcNodePath);
+        Mockito.when(reactor.syncup(Matchers.<InstanceIdentifier<FlowCapableNode>>any(),Matchers.<FlowCapableNode>any(),Matchers.<FlowCapableNode>any()))
+                .thenReturn(Futures.immediateFuture(Boolean.TRUE));
+
+        nodeListenerOperational.onDataTreeChanged(Collections.singleton(dataTreeModification));
+
+        Mockito.verify(reactor).syncup(fcNodePath, configTree, mockOperationalFlowCapableNode);
+        Mockito.verify(roTx).close();
+    }
+}
diff --git a/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/SyncReactorImplTest.java b/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/SyncReactorImplTest.java
new file mode 100644 (file)
index 0000000..94e3723
--- /dev/null
@@ -0,0 +1,804 @@
+/**
+ * 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;
+
+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.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;
+
+/**
+ * Test for {@link SyncReactorImpl}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class SyncReactorImplTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SyncReactorImplTest.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 SyncReactorImpl reactor;
+    @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;
+
+    @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();
+    }
+
+    @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());
+    }
+}
\ No newline at end of file
diff --git a/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/TableForwarderTest.java b/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/impl/TableForwarderTest.java
new file mode 100644 (file)
index 0000000..7e88950
--- /dev/null
@@ -0,0 +1,118 @@
+/**
+ * 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;
+
+import java.math.BigInteger;
+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.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+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.transaction.rev150304.TransactionId;
+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.table.service.rev131026.SalTableService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.service.rev131026.UpdateTableInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.service.rev131026.UpdateTableOutput;
+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.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.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+
+/**
+ * Test for {@link TableForwarder}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class TableForwarderTest {
+
+    private final NodeKey s1Key = new NodeKey(new NodeId("S1"));
+    private final Short tableId = (short) 42;
+    private final TableKey tableKey = new TableKey(tableId);
+    private final TableFeaturesKey tableFeaturesKey = new TableFeaturesKey(tableId);
+    private final TableFeatures tableFeatures = new TableFeaturesBuilder()
+            .setName("test-table")
+            .setTableId(tableId)
+            .build();
+
+    private final KeyedInstanceIdentifier<Node, NodeKey> nodePath = InstanceIdentifier.create(Nodes.class)
+            .child(Node.class, s1Key);
+    private final InstanceIdentifier<FlowCapableNode> flowCapableNodePath = nodePath
+            .augmentation(FlowCapableNode.class);
+    private final KeyedInstanceIdentifier<Table, TableKey> tablePath = flowCapableNodePath
+            .child(Table.class, tableKey);
+    private final InstanceIdentifier<TableFeatures> tableFeaturesPath = flowCapableNodePath
+            .child(TableFeatures.class, tableFeaturesKey);
+
+    @Captor
+    private ArgumentCaptor<UpdateTableInput> updateTableInputCpt;
+    @Mock
+    private SalTableService salTableService;
+
+    private TransactionId txId;
+
+    private TableForwarder tableForwarder;
+
+
+    @Before
+    public void setUp() throws Exception {
+        tableForwarder = new TableForwarder(salTableService);
+        txId = new TransactionId(BigInteger.ONE);
+    }
+
+    @Test
+    public void testUpdate() throws Exception {
+        Mockito.when(salTableService.updateTable(updateTableInputCpt.capture())).thenReturn(
+                RpcResultBuilder.success(
+                        new UpdateTableOutputBuilder()
+                                .setTransactionId(txId)
+                                .build()
+                ).buildFuture()
+        );
+
+        final TableFeatures tableFeaturesUpdate = new TableFeaturesBuilder(tableFeatures)
+                .setName("another-table")
+                .build();
+        final Future<RpcResult<UpdateTableOutput>> updateResult = tableForwarder.update(
+                tableFeaturesPath, tableFeatures, tableFeaturesUpdate, flowCapableNodePath);
+
+        Mockito.verify(salTableService).updateTable(Matchers.<UpdateTableInput>any());
+
+        Assert.assertTrue(updateResult.isDone());
+        final RpcResult<UpdateTableOutput> updateTableResult = updateResult.get(2, TimeUnit.SECONDS);
+        Assert.assertTrue(updateTableResult.isSuccessful());
+
+        Assert.assertEquals(1, updateTableResult.getResult().getTransactionId().getValue().intValue());
+
+        final UpdateTableInput updateTableInput = updateTableInputCpt.getValue();
+        Assert.assertEquals(tablePath, updateTableInput.getTableRef().getValue());
+        Assert.assertEquals(nodePath, updateTableInput.getNode().getValue());
+
+        Assert.assertEquals(1, updateTableInput.getOriginalTable().getTableFeatures().size());
+        Assert.assertEquals("test-table", updateTableInput.getOriginalTable().getTableFeatures().get(0).getName());
+        Assert.assertEquals(1, updateTableInput.getUpdatedTable().getTableFeatures().size());
+        Assert.assertEquals("another-table", updateTableInput.getUpdatedTable().getTableFeatures().get(0).getName());
+    }
+}
\ No newline at end of file
diff --git a/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/util/ReconcileUtilTest.java b/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/util/ReconcileUtilTest.java
new file mode 100644 (file)
index 0000000..1057973
--- /dev/null
@@ -0,0 +1,314 @@
+/**
+ * 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 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 org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+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.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.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.transaction.rev150304.FlowCapableTransactionService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev150304.SendBarrierInput;
+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.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;
+
+/**
+ * Test for {@link ReconcileUtil}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ReconcileUtilTest {
+
+    private static final NodeId NODE_ID = new NodeId("unit-node-id");
+    private InstanceIdentifier<Node> NODE_IDENT = InstanceIdentifier.create(Nodes.class)
+            .child(Node.class, new NodeKey(NODE_ID));
+
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+    @Mock
+    private FlowCapableTransactionService flowCapableService;
+    @Captor
+    private ArgumentCaptor<SendBarrierInput> barrierInputCaptor;
+
+    @Test
+    public void testChainBarrierFlush() throws Exception {
+        SettableFuture<RpcResult<Void>> testRabbit = SettableFuture.create();
+        final ListenableFuture<RpcResult<Void>> vehicle =
+                Futures.transform(testRabbit, ReconcileUtil.chainBarrierFlush(NODE_IDENT, flowCapableService));
+        Mockito.when(flowCapableService.sendBarrier(barrierInputCaptor.capture()))
+                .thenReturn(RpcResultBuilder.<Void>success().buildFuture());
+
+        Mockito.verify(flowCapableService, Mockito.never()).sendBarrier(Matchers.<SendBarrierInput>any());
+        Assert.assertFalse(vehicle.isDone());
+
+        testRabbit.set(RpcResultBuilder.<Void>success().build());
+        Mockito.verify(flowCapableService).sendBarrier(Matchers.<SendBarrierInput>any());
+        Assert.assertTrue(vehicle.isDone());
+        Assert.assertTrue(vehicle.get().isSuccessful());
+    }
+
+    @Test
+    public void testCreateRpcResultCondenser() throws Exception {
+
+    }
+
+    /**
+     * add one missing group
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testResolveAndDivideGroups1() throws Exception {
+        final Map<Long, Group> installedGroups = new HashMap<>();
+        installedGroups.put(1L, createGroup(1L));
+        installedGroups.put(2L, createGroup(2L));
+        installedGroups.put(3L, createGroup(3L));
+
+        final List<Group> pendingGroups = new ArrayList<>();
+        pendingGroups.add(createGroup(2L));
+        pendingGroups.add(createGroup(3L));
+        pendingGroups.add(createGroup(4L));
+
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+                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(0, plan.get(0).getItemsToUpdate().size());
+    }
+
+    /**
+     * add 3 groups with dependencies - 3 steps involved
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testResolveAndDivideGroups2() throws Exception {
+        final Map<Long, Group> installedGroups = new HashMap<>();
+        installedGroups.put(1L, createGroup(1L));
+
+        final List<Group> pendingGroups = new ArrayList<>();
+        pendingGroups.add(createGroup(2L));
+        pendingGroups.add(createGroupWithPreconditions(3L, 2L, 4L));
+        pendingGroups.add(createGroupWithPreconditions(4L, 2L));
+
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+                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(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(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(0, plan.get(2).getItemsToUpdate().size());
+    }
+
+    /**
+     * no actions taken - installed and pending groups are the same
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testResolveAndDivideGroups3() throws Exception {
+        final Map<Long, Group> installedGroups = new HashMap<>();
+        installedGroups.put(1L, createGroup(1L));
+        installedGroups.put(2L, createGroupWithPreconditions(2L, 1L));
+
+        final List<Group> pendingGroups = new ArrayList<>();
+        pendingGroups.add(createGroup(1L));
+        pendingGroups.add(createGroupWithPreconditions(2L, 1L));
+
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+                NODE_ID, installedGroups, pendingGroups);
+
+        Assert.assertEquals(0, plan.size());
+    }
+
+    /**
+     * update 1 group
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testResolveAndDivideGroups4() throws Exception {
+        final Map<Long, Group> installedGroups = new HashMap<>();
+        installedGroups.put(1L, createGroup(1L));
+        installedGroups.put(2L, createGroup(2L));
+
+        final List<Group> pendingGroups = new ArrayList<>();
+        pendingGroups.add(createGroupWithPreconditions(1L, 2L));
+        pendingGroups.add(createGroup(2L));
+
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+                NODE_ID, installedGroups, pendingGroups);
+
+        Assert.assertEquals(1, plan.size());
+        Assert.assertEquals(0, plan.get(0).getItemsToAdd().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());
+        Assert.assertEquals(1L, firstItemUpdateTuple.getUpdated().getGroupId().getValue().longValue());
+    }
+
+    /**
+     * no action taken - update 1 group will be ignored
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testResolveAndDivideGroups5() throws Exception {
+        final Map<Long, Group> installedGroups = new HashMap<>();
+        installedGroups.put(1L, createGroup(1L));
+        installedGroups.put(2L, createGroup(2L));
+
+        final List<Group> pendingGroups = new ArrayList<>();
+        pendingGroups.add(createGroupWithPreconditions(1L, 2L));
+        pendingGroups.add(createGroup(2L));
+
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+                NODE_ID, installedGroups, pendingGroups, false);
+
+        Assert.assertEquals(0, plan.size());
+    }
+
+    /**
+     * should add 1 group but preconditions are not met
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testResolveAndDivideGroups_negative1() throws Exception {
+        final Map<Long, Group> installedGroups = new HashMap<>();
+        installedGroups.put(1L, createGroup(1L));
+        installedGroups.put(2L, createGroup(2L));
+
+        final List<Group> pendingGroups = new ArrayList<>();
+        pendingGroups.add(createGroupWithPreconditions(3L, 4L));
+
+        thrown.expect(IllegalStateException.class);
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+                NODE_ID, installedGroups, pendingGroups);
+    }
+
+    /**
+     * should update 1 group but preconditions are not met
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testResolveAndDivideGroups_negative2() throws Exception {
+        final Map<Long, Group> installedGroups = new HashMap<>();
+        installedGroups.put(1L, createGroup(1L));
+        installedGroups.put(2L, createGroup(2L));
+
+        final List<Group> pendingGroups = new ArrayList<>();
+        pendingGroups.add(createGroupWithPreconditions(1L, 3L));
+
+        thrown.expect(IllegalStateException.class);
+        final List<ItemSyncBox<Group>> plan = ReconcileUtil.resolveAndDivideGroups(
+                NODE_ID, installedGroups, pendingGroups);
+    }
+
+    @Test
+    public void testCheckGroupPrecondition() throws Exception {
+        final Set<Long> installedGroups = new HashSet<>(Arrays.asList(new Long[]{1L, 2L}));
+
+        final Group pendingGroup1 = createGroupWithPreconditions(3L, 2L, 4L);
+        Assert.assertFalse(ReconcileUtil.checkGroupPrecondition(installedGroups, pendingGroup1));
+
+        final Group pendingGroup2 = createGroupWithPreconditions(1L, 2L);
+        Assert.assertTrue(ReconcileUtil.checkGroupPrecondition(installedGroups, pendingGroup2));
+
+        final Group pendingGroup3 = createGroupWithPreconditions(1L);
+        Assert.assertTrue(ReconcileUtil.checkGroupPrecondition(installedGroups, pendingGroup3));
+    }
+
+    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();
+    }
+
+    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();
+    }
+}
\ No newline at end of file
diff --git a/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/util/SemaphoreKeeperTest.java b/applications/forwardingrules-sync/src/test/java/org/opendaylight/openflowplugin/applications/frsync/util/SemaphoreKeeperTest.java
new file mode 100644 (file)
index 0000000..f874eb7
--- /dev/null
@@ -0,0 +1,143 @@
+/**
+ * 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 java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.openflowplugin.applications.frsync.SemaphoreKeeper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test for {@link SemaphoreKeeperGuavaImpl}.
+ */
+public class SemaphoreKeeperTest {
+    private static final Logger LOG = LoggerFactory.getLogger(SemaphoreKeeperTest.class);
+    private SemaphoreKeeperGuavaImpl<String> semaphoreKeeper;
+    final String key = "11";
+
+    @Before
+    public void setUp() throws Exception {
+        semaphoreKeeper = new SemaphoreKeeperGuavaImpl(1, true);
+    }
+
+    @Test
+    public void testSummonGuard() throws Exception {
+        Semaphore semaphore1 = semaphoreKeeper.summonGuard(key);
+        final int g1FingerPrint = semaphore1.hashCode();
+        Semaphore semaphore2 = semaphoreKeeper.summonGuard(key);
+        final int g2FingerPrint = semaphore2.hashCode();
+
+        Assert.assertSame(semaphore1, semaphore2);
+        Assert.assertEquals(1, semaphore1.availablePermits());
+
+        semaphore1.acquire();
+        semaphore1.release();
+        Assert.assertEquals(1, semaphore1.availablePermits());
+        semaphore1 = null;
+        System.gc();
+
+        semaphore2.acquire();
+        semaphore2.release();
+        Assert.assertEquals(1, semaphore2.availablePermits());
+        semaphore2 = null;
+        Assert.assertEquals(g1FingerPrint, g2FingerPrint);
+
+        System.gc();
+        final Semaphore semaphore3 = semaphoreKeeper.summonGuard(key);
+        Assert.assertNotEquals(g1FingerPrint, semaphore3.hashCode());
+    }
+
+    @Test
+    public void testReleaseGuard() throws Exception {
+        for (int total = 1; total <= 10; total++) {
+            LOG.info("test run: {}", total);
+            final Worker task = new Worker(semaphoreKeeper, key);
+
+            final ExecutorService executorService = new ThreadPoolExecutor(5, 5,
+                    0L, TimeUnit.MILLISECONDS,
+                    new LinkedBlockingQueue<Runnable>()) {
+                @Override
+                protected void afterExecute(final Runnable r, final Throwable t) {
+                    super.afterExecute(r, t);
+                    if (t != null) {
+                        LOG.error("pool thread crashed", t);
+                    }
+                }
+            };
+
+            final int steps = 10;
+            for (int i = 0; i < steps; i++) {
+                executorService.submit(task);
+            }
+            Thread.sleep(50L);
+            LOG.info("STARTING new serie");
+            System.gc();
+
+            for (int i = 0; i < steps; i++) {
+                executorService.submit(task);
+            }
+            Thread.sleep(1000L);
+            System.gc();
+
+            executorService.shutdown();
+            final boolean terminated = executorService.awaitTermination(10, TimeUnit.SECONDS);
+            if (!terminated) {
+                LOG.warn("pool stuck, forcing termination");
+                executorService.shutdownNow();
+                Assert.fail("pool failed to finish gracefully");
+            }
+
+            final int counterSize = task.getCounterSize();
+            LOG.info("final counter = {}", counterSize);
+            Assert.assertEquals(20, counterSize);
+        }
+    }
+
+    private static class Worker implements Runnable {
+        private final SemaphoreKeeper<String> keeper;
+        private final String key;
+        private ConcurrentMap<Integer, Integer> counter = new ConcurrentHashMap<>();
+        private volatile int index = 0;
+
+        public Worker(SemaphoreKeeper<String> keeper, final String key) {
+            this.keeper = keeper;
+            this.key = key;
+        }
+
+        @Override
+        public void run() {
+            try {
+                final Semaphore guard = keeper.summonGuard(key);
+                Thread.sleep(2L);
+                guard.acquire();
+                counter.putIfAbsent(index, 0);
+                counter.put(index, counter.get(index) + 1);
+                LOG.debug("queue: {} [{}] - {}", guard.getQueueLength(), guard.hashCode(), counter.size());
+                index++;
+                guard.release();
+            } catch (Exception e) {
+                LOG.warn("acquiring failed.. ", e);
+            }
+        }
+
+        public int getCounterSize() {
+            return counter.size();
+        }
+    }
+}
\ No newline at end of file
diff --git a/applications/forwardingrules-sync/src/test/resources/log4j.xml b/applications/forwardingrules-sync/src/test/resources/log4j.xml
new file mode 100644 (file)
index 0000000..f17195f
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">\r
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">\r
+\r
+    <appender name="console" class="org.apache.log4j.ConsoleAppender">\r
+        <layout class="org.apache.log4j.PatternLayout">\r
+            <param name="ConversionPattern" value="%-6p %d{HH:mm:ss.SSS} [%10.10t] %30.30c %x - %m%n"/>\r
+        </layout>\r
+    </appender>\r
+\r
+    <logger name="org.opendaylight.openflowplugin.applications.frsync" additivity="false">\r
+        <level value="TRACE"/>\r
+        <appender-ref ref="console"/>\r
+    </logger>\r
+\r
+    <root>\r
+        <priority value="INFO"/>\r
+        <appender-ref ref="console"/>\r
+    </root>\r
+</log4j:configuration>
\ No newline at end of file