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 {
+ }
+}