Add unit tests for OpenstackFlowRuleManager

Change-Id: If7c89cd0eb0115972dcefd6bc026414e34029df2
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java
index 4c5b0b7..a1289c4 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java
@@ -22,6 +22,9 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.net.DeviceId;
@@ -42,11 +45,13 @@
 import org.onosproject.openstacknode.api.OpenstackNodeService;
 import org.slf4j.Logger;
 
+import java.util.Objects;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -69,21 +74,31 @@
     protected CoreService coreService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected OpenstackNodeService osNodeService;
 
     private final ExecutorService deviceEventExecutor =
-            Executors.newSingleThreadExecutor(groupedThreads("openstacknetworking", "device-event"));
-    private final OpenstackNodeListener internalNodeListener = new InternalOpenstackNodeListener();
+            Executors.newSingleThreadExecutor(groupedThreads(
+                    getClass().getSimpleName(), "device-event"));
+    private final OpenstackNodeListener internalNodeListener =
+                                        new InternalOpenstackNodeListener();
 
     private ApplicationId appId;
+    private NodeId localNodeId;
 
     @Activate
     protected void activate() {
         appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
         coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
         osNodeService.addListener(internalNodeListener);
-
-        osNodeService.completeNodes(OpenstackNode.NodeType.COMPUTE)
+        localNodeId = clusterService.getLocalNode().id();
+        leadershipService.runForLeadership(appId.name());
+        osNodeService.completeNodes(COMPUTE)
                 .forEach(node -> initializePipeline(node.intgBridge()));
 
         log.info("Started");
@@ -92,20 +107,20 @@
     @Deactivate
     protected void deactivate() {
         osNodeService.removeListener(internalNodeListener);
+        leadershipService.withdraw(appId.name());
         deviceEventExecutor.shutdown();
 
         log.info("Stopped");
     }
 
-
     @Override
     public void setRule(ApplicationId appId,
-                               DeviceId deviceId,
-                               TrafficSelector selector,
-                               TrafficTreatment treatment,
-                               int priority,
-                               int tableType,
-                               boolean install) {
+                        DeviceId deviceId,
+                        TrafficSelector selector,
+                        TrafficTreatment treatment,
+                        int priority,
+                        int tableType,
+                        boolean install) {
 
         FlowRule.Builder flowRuleBuilder = DefaultFlowRule.builder()
                 .forDevice(deviceId)
@@ -124,49 +139,6 @@
         applyRule(flowRuleBuilder.build(), install);
     }
 
-    private void applyRule(FlowRule flowRule, boolean install) {
-        FlowRuleOperations.Builder flowOpsBuilder = FlowRuleOperations.builder();
-
-        flowOpsBuilder = install ? flowOpsBuilder.add(flowRule) : flowOpsBuilder.remove(flowRule);
-
-        flowRuleService.apply(flowOpsBuilder.build(new FlowRuleOperationsContext() {
-            @Override
-            public void onSuccess(FlowRuleOperations ops) {
-                log.debug("Provisioned vni or forwarding table");
-            }
-
-            @Override
-            public void onError(FlowRuleOperations ops) {
-                log.debug("Failed to provision vni or forwarding table");
-            }
-        }));
-    }
-
-    private void initializePipeline(DeviceId deviceId) {
-        // for inbound table transition
-        connectTables(deviceId, Constants.STAT_INBOUND_TABLE, Constants.VTAP_INBOUND_TABLE);
-        connectTables(deviceId, Constants.VTAP_INBOUND_TABLE, Constants.DHCP_ARP_TABLE);
-
-        // for vTag and ACL table transition
-        connectTables(deviceId, Constants.DHCP_ARP_TABLE, Constants.VTAG_TABLE);
-        connectTables(deviceId, Constants.VTAG_TABLE, Constants.ACL_TABLE);
-        connectTables(deviceId, Constants.ACL_TABLE, Constants.JUMP_TABLE);
-
-        // for JUMP table transition
-        // we need JUMP table for bypassing routing table which contains large
-        // amount of flow rules which might cause performance degradation during
-        // table lookup
-        setupJumpTable(deviceId);
-
-        // for outbound table transition
-        connectTables(deviceId, Constants.STAT_OUTBOUND_TABLE, Constants.VTAP_OUTBOUND_TABLE);
-        connectTables(deviceId, Constants.VTAP_OUTBOUND_TABLE, Constants.FORWARDING_TABLE);
-
-        // for FLAT outbound table transition
-        connectTables(deviceId, Constants.STAT_FLAT_OUTBOUND_TABLE, Constants.VTAP_FLAT_OUTBOUND_TABLE);
-        connectTables(deviceId, Constants.VTAP_FLAT_OUTBOUND_TABLE, Constants.FLAT_TABLE);
-    }
-
     @Override
     public void connectTables(DeviceId deviceId, int fromTable, int toTable) {
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
@@ -207,6 +179,49 @@
         applyRule(flowRule, true);
     }
 
+    private void applyRule(FlowRule flowRule, boolean install) {
+        FlowRuleOperations.Builder flowOpsBuilder = FlowRuleOperations.builder();
+
+        flowOpsBuilder = install ? flowOpsBuilder.add(flowRule) : flowOpsBuilder.remove(flowRule);
+
+        flowRuleService.apply(flowOpsBuilder.build(new FlowRuleOperationsContext() {
+            @Override
+            public void onSuccess(FlowRuleOperations ops) {
+                log.debug("Provisioned vni or forwarding table");
+            }
+
+            @Override
+            public void onError(FlowRuleOperations ops) {
+                log.debug("Failed to provision vni or forwarding table");
+            }
+        }));
+    }
+
+    protected void initializePipeline(DeviceId deviceId) {
+        // for inbound table transition
+        connectTables(deviceId, Constants.STAT_INBOUND_TABLE, Constants.VTAP_INBOUND_TABLE);
+        connectTables(deviceId, Constants.VTAP_INBOUND_TABLE, Constants.DHCP_ARP_TABLE);
+
+        // for vTag and ACL table transition
+        connectTables(deviceId, Constants.DHCP_ARP_TABLE, Constants.VTAG_TABLE);
+        connectTables(deviceId, Constants.VTAG_TABLE, Constants.ACL_TABLE);
+        connectTables(deviceId, Constants.ACL_TABLE, Constants.JUMP_TABLE);
+
+        // for JUMP table transition
+        // we need JUMP table for bypassing routing table which contains large
+        // amount of flow rules which might cause performance degradation during
+        // table lookup
+        setupJumpTable(deviceId);
+
+        // for outbound table transition
+        connectTables(deviceId, Constants.STAT_OUTBOUND_TABLE, Constants.VTAP_OUTBOUND_TABLE);
+        connectTables(deviceId, Constants.VTAP_OUTBOUND_TABLE, Constants.FORWARDING_TABLE);
+
+        // for FLAT outbound table transition
+        connectTables(deviceId, Constants.STAT_FLAT_OUTBOUND_TABLE, Constants.VTAP_FLAT_OUTBOUND_TABLE);
+        connectTables(deviceId, Constants.VTAP_FLAT_OUTBOUND_TABLE, Constants.FLAT_TABLE);
+    }
+
     private void setupJumpTable(DeviceId deviceId) {
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
@@ -247,15 +262,22 @@
     private class InternalOpenstackNodeListener implements OpenstackNodeListener {
 
         @Override
+        public boolean isRelevant(OpenstackNodeEvent event) {
+            // do not allow to proceed without leadership
+            NodeId leader = leadershipService.getLeader(appId.name());
+            return Objects.equals(localNodeId, leader) &&
+                    event.subject().type().equals(COMPUTE);
+        }
+
+        @Override
         public void event(OpenstackNodeEvent event) {
             OpenstackNode osNode = event.subject();
-            // TODO check leadership of the node and make only the leader process
 
             switch (event.type()) {
                 case OPENSTACK_NODE_COMPLETE:
                     deviceEventExecutor.execute(() -> {
                         log.info("COMPLETE node {} is detected", osNode.hostname());
-                        processCompleteNode(event.subject());
+                        initializePipeline(osNode.intgBridge());
                     });
                     break;
                 case OPENSTACK_NODE_CREATED:
@@ -267,11 +289,5 @@
                     break;
             }
         }
-
-        private void processCompleteNode(OpenstackNode osNode) {
-            if (osNode.type().equals(OpenstackNode.NodeType.COMPUTE)) {
-                initializePipeline(osNode.intgBridge());
-            }
-        }
     }
 }
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManagerTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManagerTest.java
new file mode 100644
index 0000000..e3e449b
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManagerTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.cluster.LeadershipServiceAdapter;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleOperation;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleServiceAdapter;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.openstacknetworking.api.Constants.ACL_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.DHCP_ARP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.FLAT_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.FORWARDING_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.JUMP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.STAT_FLAT_OUTBOUND_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.STAT_INBOUND_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.STAT_OUTBOUND_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.VTAG_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.VTAP_FLAT_OUTBOUND_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.VTAP_INBOUND_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.VTAP_OUTBOUND_TABLE;
+
+/**
+ * Unit tests for flow rule manager.
+ */
+public class OpenstackFlowRuleManagerTest {
+
+    private static final ApplicationId TEST_APP_ID =
+                                        new DefaultApplicationId(1, "test");
+
+    private static final int DROP_PRIORITY = 0;
+
+    private static final DeviceId DEVICE_ID = DeviceId.deviceId("of:000000000000000a");
+
+    private OpenstackFlowRuleManager target;
+
+    private Set<FlowRuleOperation> fros;
+
+    /**
+     * Initial setup for this unit test.
+     */
+    @Before
+    public void setUp() {
+        target = new OpenstackFlowRuleManager();
+        TestUtils.setField(target, "coreService", new TestCoreService());
+        TestUtils.setField(target, "flowRuleService", new TestFlowRuleService());
+        TestUtils.setField(target, "clusterService", new TestClusterService());
+        TestUtils.setField(target, "leadershipService", new TestLeadershipService());
+        TestUtils.setField(target, "osNodeService", new TestOpenstackNodeService());
+        TestUtils.setField(target, "deviceEventExecutor", MoreExecutors.newDirectExecutorService());
+
+        target.activate();
+    }
+
+    /**
+     * Tears down of this unit test.
+     */
+    @After
+    public void tearDown() {
+        target.deactivate();
+        target = null;
+    }
+
+    /**
+     * Tests whether the set rule method installs the flow rules properly.
+     */
+    @Test
+    public void testSetRule() {
+        int testPriority = 10;
+        int testTableType = 10;
+
+        fros = Sets.newConcurrentHashSet();
+
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+
+        FlowRule.Builder flowRuleBuilder = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .withSelector(selectorBuilder.build())
+                .withTreatment(treatmentBuilder.build())
+                .withPriority(testPriority)
+                .fromApp(TEST_APP_ID)
+                .forTable(testTableType)
+                .makePermanent();
+
+        target.setRule(TEST_APP_ID, DEVICE_ID, selectorBuilder.build(),
+                treatmentBuilder.build(), testPriority, testTableType, true);
+        validateFlowRule(flowRuleBuilder.build());
+    }
+
+    /**
+     * Tests whether the connect tables method installs the flow rules properly.
+     */
+    @Test
+    public void testConnectTables() {
+        int testFromTable = 1;
+        int testToTable = 2;
+
+        fros = Sets.newConcurrentHashSet();
+
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+
+        target.connectTables(DEVICE_ID, testFromTable, testToTable);
+
+        FlowRule.Builder flowRuleBuilder = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .withSelector(selectorBuilder.build())
+                .withTreatment(treatmentBuilder.transition(testToTable).build())
+                .withPriority(DROP_PRIORITY)
+                .fromApp(TEST_APP_ID)
+                .forTable(testFromTable)
+                .makePermanent();
+
+        validateFlowRule(flowRuleBuilder.build());
+    }
+
+    /**
+     * Tests whether the setup table miss entry method installs the flow rules properly.
+     */
+    @Test
+    public void testSetUpTableMissEntry() {
+        int testTable = 10;
+
+        fros = Sets.newConcurrentHashSet();
+
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+
+        target.setUpTableMissEntry(DEVICE_ID, testTable);
+
+        FlowRule.Builder flowRuleBuilder = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .withSelector(selectorBuilder.build())
+                .withTreatment(treatmentBuilder.drop().build())
+                .withPriority(DROP_PRIORITY)
+                .fromApp(TEST_APP_ID)
+                .forTable(testTable)
+                .makePermanent();
+
+        validateFlowRule(flowRuleBuilder.build());
+    }
+
+    /**
+     * Tests whether initialize pipeline method installs the flow rules properly.
+     */
+    @Test
+    public void testInitializePipeline() {
+
+        fros = Sets.newConcurrentHashSet();
+
+        target.initializePipeline(DEVICE_ID);
+        assertEquals("Flow Rule size was not match", 11, fros.size());
+
+        Map<Integer, Integer> fromToTableMap = Maps.newConcurrentMap();
+        fromToTableMap.put(STAT_INBOUND_TABLE, VTAP_INBOUND_TABLE);
+        fromToTableMap.put(VTAP_INBOUND_TABLE, DHCP_ARP_TABLE);
+        fromToTableMap.put(DHCP_ARP_TABLE, VTAG_TABLE);
+        fromToTableMap.put(VTAG_TABLE, ACL_TABLE);
+        fromToTableMap.put(ACL_TABLE, JUMP_TABLE);
+        fromToTableMap.put(STAT_OUTBOUND_TABLE, VTAP_OUTBOUND_TABLE);
+        fromToTableMap.put(VTAP_OUTBOUND_TABLE, FORWARDING_TABLE);
+        fromToTableMap.put(STAT_FLAT_OUTBOUND_TABLE, VTAP_FLAT_OUTBOUND_TABLE);
+        fromToTableMap.put(VTAP_FLAT_OUTBOUND_TABLE, FLAT_TABLE);
+
+        fros.stream().map(FlowRuleOperation::rule).forEach(fr -> {
+            if (fr.tableId() != JUMP_TABLE) {
+                assertEquals("To Table did not match,",
+                        fromToTableMap.get(fr.tableId()),
+                        fr.treatment().tableTransition().tableId());
+            }
+        });
+    }
+
+    private void validateFlowRule(FlowRule ref) {
+        assertEquals("Flow Rule size was not match", 1, fros.size());
+        List<FlowRuleOperation> froList = Lists.newArrayList();
+        froList.addAll(fros);
+        FlowRuleOperation fro = froList.get(0);
+        FlowRule fr = fro.rule();
+
+        assertEquals("Application ID did not match", ref.appId(), fr.appId());
+        assertEquals("Device ID did not match", ref.deviceId(), fr.deviceId());
+        assertEquals("Selector did not match", ref.selector(), fr.selector());
+        assertEquals("Treatment did not match", ref.treatment(), fr.treatment());
+        assertEquals("Priority did not match", ref.priority(), fr.priority());
+        assertEquals("Table ID did not match", ref.table(), fr.table());
+        assertEquals("Permanent did not match", ref.isPermanent(), fr.isPermanent());
+    }
+
+    private class TestOpenstackNodeService extends OpenstackNodeServiceAdapter {
+    }
+
+    private class TestFlowRuleService extends FlowRuleServiceAdapter {
+        @Override
+        public void apply(FlowRuleOperations ops) {
+            fros.addAll(ops.stages().get(0));
+        }
+    }
+
+    private class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public ApplicationId registerApplication(String name) {
+            return TEST_APP_ID;
+        }
+    }
+
+    private class TestClusterService extends ClusterServiceAdapter {
+    }
+
+    private class TestLeadershipService extends LeadershipServiceAdapter {
+    }
+}