ONOS-6605 PI flow rule translator implementation
Change-Id: Icac66f17677c494152207f4b52355ad647e1227b
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/MockInterpreter.java b/core/net/src/test/java/org/onosproject/net/pi/impl/MockInterpreter.java
new file mode 100644
index 0000000..ff8ac93
--- /dev/null
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/MockInterpreter.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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.net.pi.impl;
+
+import com.google.common.collect.ImmutableBiMap;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.runtime.PiTableAction;
+import org.onosproject.net.pi.runtime.PiTableId;
+
+import java.util.Optional;
+
+import static org.onosproject.net.PortNumber.CONTROLLER;
+import static org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+
+/**
+ * Mock interpreter implementation.
+ */
+public class MockInterpreter extends AbstractHandlerBehaviour implements PiPipelineInterpreter {
+
+ static final String TABLE0 = "table0";
+ static final String SEND_TO_CPU = "send_to_cpu_0";
+ static final String PORT = "port";
+ static final String DROP = "_drop_0";
+ static final String SET_EGRESS_PORT = "set_egress_port_0";
+
+ static final PiHeaderFieldId IN_PORT_ID = PiHeaderFieldId.of("standard_metadata", "ingress_port");
+ static final PiHeaderFieldId ETH_DST_ID = PiHeaderFieldId.of("ethernet_t", "dstAddr");
+ static final PiHeaderFieldId ETH_SRC_ID = PiHeaderFieldId.of("ethernet_t", "srcAddr");
+ static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of("ethernet_t", "etherType");
+
+ private static final ImmutableBiMap<Criterion.Type, PiHeaderFieldId> CRITERION_MAP = ImmutableBiMap.of(
+ Criterion.Type.IN_PORT, IN_PORT_ID,
+ Criterion.Type.ETH_DST, ETH_DST_ID,
+ Criterion.Type.ETH_SRC, ETH_SRC_ID,
+ Criterion.Type.ETH_TYPE, ETH_TYPE_ID);
+
+ private static final ImmutableBiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
+ 0, PiTableId.of(TABLE0));
+
+ @Override
+ public PiTableAction mapTreatment(TrafficTreatment treatment, PiPipeconf pipeconf) throws PiInterpreterException {
+
+ if (treatment.allInstructions().size() == 0) {
+ // No instructions means drop for us.
+ return actionWithName(DROP);
+ } else if (treatment.allInstructions().size() > 1) {
+ // Otherwise, we understand treatments with only 1 instruction.
+ throw new PiPipelineInterpreter.PiInterpreterException("Treatment has multiple instructions");
+ }
+
+ Instruction instruction = treatment.allInstructions().get(0);
+
+ switch (instruction.type()) {
+ case OUTPUT:
+ OutputInstruction outInstruction = (OutputInstruction) instruction;
+ PortNumber port = outInstruction.port();
+ if (!port.isLogical()) {
+ PiAction.builder()
+ .withId(PiActionId.of(SET_EGRESS_PORT))
+ .withParameter(new PiActionParam(PiActionParamId.of(PORT),
+ ImmutableByteSequence.copyFrom(port.toLong())))
+ .build();
+ } else if (port.equals(CONTROLLER)) {
+ return actionWithName(SEND_TO_CPU);
+ } else {
+ throw new PiInterpreterException("Egress on logical port not supported: " + port);
+ }
+ case NOACTION:
+ return actionWithName(DROP);
+ default:
+ throw new PiInterpreterException("Instruction type not supported: " + instruction.type().name());
+ }
+ }
+
+ /**
+ * Returns an action instance with no runtime parameters.
+ */
+ private PiAction actionWithName(String name) {
+ return PiAction.builder().withId(PiActionId.of(name)).build();
+ }
+
+ @Override
+ public Optional<PiHeaderFieldId> mapCriterionType(Criterion.Type type) {
+ return Optional.ofNullable(CRITERION_MAP.get(type));
+ }
+
+ @Override
+ public Optional<Criterion.Type> mapPiHeaderFieldId(PiHeaderFieldId headerFieldId) {
+ return Optional.ofNullable(CRITERION_MAP.inverse().get(headerFieldId));
+ }
+
+ @Override
+ public Optional<PiTableId> mapFlowRuleTableId(int flowRuleTableId) {
+ return Optional.ofNullable(TABLE_MAP.get(flowRuleTableId));
+ }
+
+}
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/MockPipeconf.java b/core/net/src/test/java/org/onosproject/net/pi/impl/MockPipeconf.java
new file mode 100644
index 0000000..6694215
--- /dev/null
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/MockPipeconf.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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.net.pi.impl;
+
+import com.eclipsesource.json.Json;
+import com.google.common.collect.Maps;
+import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiPipelineModel;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Mock pipeconf implementation.
+ */
+public class MockPipeconf implements PiPipeconf {
+
+ private static final String PIPECONF_ID = "org.project.pipeconf.default";
+ private static final String JSON_PATH = "/org/onosproject/net/pi/impl/default.json";
+
+ private final PiPipeconfId id;
+ private final PiPipelineModel pipelineModel;
+ private final Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours;
+
+ public MockPipeconf() throws IOException {
+ this.id = new PiPipeconfId(PIPECONF_ID);
+ this.pipelineModel = loadDefaultModel();
+ this.behaviours = Maps.newHashMap();
+
+ behaviours.put(PiPipelineInterpreter.class, MockInterpreter.class);
+ }
+
+ static PiPipelineModel loadDefaultModel() throws IOException {
+ return Bmv2PipelineModelParser.parse(Json.parse(new BufferedReader(new InputStreamReader(
+ MockPipeconf.class.getResourceAsStream(JSON_PATH)))).asObject());
+ }
+
+ @Override
+ public PiPipeconfId id() {
+ return this.id;
+ }
+
+ @Override
+ public PiPipelineModel pipelineModel() {
+ return pipelineModel;
+ }
+
+ @Override
+ public Collection<Class<? extends Behaviour>> behaviours() {
+ return behaviours.values();
+ }
+
+ @Override
+ public Optional<Class<? extends Behaviour>> implementation(Class<? extends Behaviour> behaviour) {
+ return Optional.ofNullable(behaviours.get(behaviour));
+ }
+
+ @Override
+ public boolean hasBehaviour(Class<? extends Behaviour> behaviourClass) {
+ return behaviours.containsKey(behaviourClass);
+ }
+
+ @Override
+ public Optional<ByteBuffer> extension(ExtensionType type) {
+ return Optional.empty();
+ }
+}
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorTest.java b/core/net/src/test/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorTest.java
new file mode 100644
index 0000000..547e957
--- /dev/null
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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.net.pi.impl;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+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.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiTableEntry;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+
+import java.util.Optional;
+import java.util.Random;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.onosproject.net.pi.impl.MockInterpreter.*;
+
+/**
+ * Tests for {@link PiFlowRuleTranslator}.
+ */
+@SuppressWarnings("ConstantConditions")
+public class PiFlowRuleTranslatorTest {
+
+ private Random random = new Random();
+ private PiPipeconf pipeconf;
+
+ @Before
+ public void setUp() throws Exception {
+ pipeconf = new MockPipeconf();
+ }
+
+ @Test
+ public void testTranslate() throws Exception {
+
+ DeviceId deviceId = DeviceId.NONE;
+ ApplicationId appId = new DefaultApplicationId(1, "test");
+ int tableId = 0;
+ MacAddress ethDstMac = MacAddress.valueOf(random.nextLong());
+ MacAddress ethSrcMac = MacAddress.valueOf(random.nextLong());
+ short ethType = (short) (0x0000FFFF & random.nextInt());
+ short outPort = (short) random.nextInt(65);
+ short inPort = (short) random.nextInt(65);
+ int timeout = random.nextInt(100);
+ int priority = random.nextInt(100);
+
+ TrafficSelector matchInPort1 = DefaultTrafficSelector
+ .builder()
+ .matchInPort(PortNumber.portNumber(inPort))
+ .matchEthDst(ethDstMac)
+ .matchEthSrc(ethSrcMac)
+ .matchEthType(ethType)
+ .build();
+
+ TrafficTreatment outPort2 = DefaultTrafficTreatment
+ .builder()
+ .setOutput(PortNumber.portNumber(outPort))
+ .build();
+
+ FlowRule rule1 = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .forTable(tableId)
+ .fromApp(appId)
+ .withSelector(matchInPort1)
+ .withTreatment(outPort2)
+ .makeTemporary(timeout)
+ .withPriority(priority)
+ .build();
+
+ FlowRule rule2 = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .forTable(tableId)
+ .fromApp(appId)
+ .withSelector(matchInPort1)
+ .withTreatment(outPort2)
+ .makeTemporary(timeout)
+ .withPriority(priority)
+ .build();
+
+ PiTableEntry entry1 = PiFlowRuleTranslator.translateFlowRule(rule1, pipeconf, null);
+ PiTableEntry entry2 = PiFlowRuleTranslator.translateFlowRule(rule1, pipeconf, null);
+
+ // check equality, i.e. same rules must produce same entries
+ new EqualsTester()
+ .addEqualityGroup(rule1, rule2)
+ .addEqualityGroup(entry1, entry2)
+ .testEquals();
+
+ int numMatchParams = pipeconf.pipelineModel().table(TABLE0).get().matchFields().size();
+ // parse values stored in entry1
+ PiTernaryFieldMatch inPortParam = (PiTernaryFieldMatch) entry1.fieldMatch(IN_PORT_ID).get();
+ PiTernaryFieldMatch ethDstParam = (PiTernaryFieldMatch) entry1.fieldMatch(ETH_DST_ID).get();
+ PiTernaryFieldMatch ethSrcParam = (PiTernaryFieldMatch) entry1.fieldMatch(ETH_SRC_ID).get();
+ PiTernaryFieldMatch ethTypeParam = (PiTernaryFieldMatch) entry1.fieldMatch(ETH_TYPE_ID).get();
+ Optional<Double> expectedTimeout = pipeconf.pipelineModel().table(TABLE0).get().supportsAging()
+ ? Optional.of((double) rule1.timeout()) : Optional.empty();
+
+ // check that the number of parameters in the entry is the same as the number of table keys
+ assertThat("Incorrect number of match parameters",
+ entry1.fieldMatches().size(), is(equalTo(numMatchParams)));
+
+ // check that values stored in entry are the same used for the flow rule
+ assertThat("Incorrect inPort match param value",
+ inPortParam.value().asReadOnlyBuffer().getShort(), is(equalTo(inPort)));
+ assertThat("Incorrect ethDestMac match param value",
+ ethDstParam.value().asArray(), is(equalTo(ethDstMac.toBytes())));
+ assertThat("Incorrect ethSrcMac match param value",
+ ethSrcParam.value().asArray(), is(equalTo(ethSrcMac.toBytes())));
+ assertThat("Incorrect ethType match param value",
+ ethTypeParam.value().asReadOnlyBuffer().getShort(), is(equalTo(ethType)));
+ assertThat("Incorrect priority value",
+ entry1.priority().get(), is(equalTo(rule1.priority())));
+ assertThat("Incorrect timeout value",
+ entry1.timeout(), is(equalTo(expectedTimeout)));
+
+ }
+}