[ONOS-7583]Adding PI flow rules via REST API and unit tests
Change-Id: I15537097e7b6f8138f8ae43676d5dd22d8b9093a
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java
index d61f6c1..c748ab6 100644
--- a/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java
+++ b/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java
@@ -15,8 +15,14 @@
*/
package org.onosproject.codec.impl;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
import java.util.EnumMap;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.hamcrest.MatcherAssert;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.Ip6Address;
@@ -38,10 +44,20 @@
import org.onosproject.net.flow.criteria.Criterion;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.runtime.PiExactFieldMatch;
+import org.onosproject.net.pi.runtime.PiFieldMatch;
+import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
+import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+import org.onosproject.net.pi.runtime.PiValidFieldMatch;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.onlab.junit.TestUtils.getField;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
import static org.onosproject.codec.impl.CriterionJsonMatcher.matchesCriterion;
/**
@@ -460,4 +476,102 @@
assertThat(result, matchesCriterion(criterion));
}
+ /**
+ * Tests protocol-independent type criterion encoding.
+ */
+ @Test
+ public void matchPiTypeEncodingTest() {
+
+ PiMatchFieldId ethMatchFieldId = PiMatchFieldId.of("ethernet_t.etherType");
+ byte[] matchExactBytes1 = {0x08, 0x00};
+ Criterion exactBytesCriterion = PiCriterion.builder().matchExact(ethMatchFieldId, matchExactBytes1).build();
+ ObjectNode exactResult = criterionCodec.encode(exactBytesCriterion, context);
+ assertThat(exactResult, matchesCriterion(exactBytesCriterion));
+
+ PiMatchFieldId ipv4MatchFieldId = PiMatchFieldId.of("ipv4_t.dstAddr");
+ int mask = 0x00ffffff;
+ byte[] matchLpmBytes1 = {0x0a, 0x01, 0x01, 0x01};
+ Criterion lpmBytesCriterion = PiCriterion.builder().matchLpm(ipv4MatchFieldId, matchLpmBytes1, mask).build();
+ ObjectNode lpmResult = criterionCodec.encode(lpmBytesCriterion, context);
+ assertThat(lpmResult, matchesCriterion(lpmBytesCriterion));
+
+ byte[] matchTernaryBytes1 = {0x0a, 0x01, 0x01, 0x01};
+ byte[] matchTernaryMaskBytes = {0x7f, 0x7f, 0x7f, 0x00};
+ Criterion ternaryBytesCriterion = PiCriterion.builder().matchTernary(ipv4MatchFieldId, matchTernaryBytes1,
+ matchTernaryMaskBytes).build();
+ ObjectNode ternaryResult = criterionCodec.encode(ternaryBytesCriterion, context);
+ assertThat(ternaryResult, matchesCriterion(ternaryBytesCriterion));
+
+ byte[] matchRangeBytes1 = {0x10};
+ byte[] matchRangeHighBytes = {0x30};
+ Criterion rangeBytesCriterion = PiCriterion.builder()
+ .matchRange(ipv4MatchFieldId, matchRangeBytes1, matchRangeHighBytes).build();
+ ObjectNode rangeResult = criterionCodec.encode(rangeBytesCriterion, context);
+ assertThat(rangeResult, matchesCriterion(rangeBytesCriterion));
+
+ Criterion validCriterion = PiCriterion.builder().matchValid(ipv4MatchFieldId, false).build();
+ ObjectNode validResult = criterionCodec.encode(validCriterion, context);
+ assertThat(validResult, matchesCriterion(validCriterion));
+ }
+
+ /**
+ * Tests protocol-independent type criterion decoding.
+ */
+ @Test
+ public void matchPiTypeDecodingTest() throws IOException {
+ Criterion criterion = getCriterion("PiCriterion.json");
+ Assert.assertThat(criterion.type(), is(Criterion.Type.PROTOCOL_INDEPENDENT));
+ Collection<PiFieldMatch> piFieldMatches = ((PiCriterion) criterion).fieldMatches();
+ for (PiFieldMatch piFieldMatch : piFieldMatches) {
+ switch (piFieldMatch.type()) {
+ case EXACT:
+ Assert.assertThat(piFieldMatch.fieldId().id(), is("ingress_port"));
+ Assert.assertThat(((PiExactFieldMatch) piFieldMatch).value(), is(
+ copyFrom((byte) 0x10)));
+ break;
+ case LPM:
+ Assert.assertThat(piFieldMatch.fieldId().id(), is("src_addr"));
+ Assert.assertThat(((PiLpmFieldMatch) piFieldMatch).value(),
+ is(copyFrom(0xa010101)));
+ Assert.assertThat(((PiLpmFieldMatch) piFieldMatch).prefixLength(), is(24));
+ break;
+ case TERNARY:
+ Assert.assertThat(piFieldMatch.fieldId().id(), is("dst_addr"));
+ Assert.assertThat(((PiTernaryFieldMatch) piFieldMatch).value(),
+ is(copyFrom(0xa010101)));
+ Assert.assertThat(((PiTernaryFieldMatch) piFieldMatch).mask(),
+ is(copyFrom(0xfffffff)));
+ break;
+ case RANGE:
+ Assert.assertThat(piFieldMatch.fieldId().id(), is("egress_port"));
+ Assert.assertThat(((PiRangeFieldMatch) piFieldMatch).highValue(),
+ is(copyFrom((byte) 0x20)));
+ Assert.assertThat(((PiRangeFieldMatch) piFieldMatch).lowValue(),
+ is(copyFrom((byte) 0x10)));
+ break;
+ case VALID:
+ Assert.assertThat(piFieldMatch.fieldId().id(), is("ethernet_t.etherType"));
+ Assert.assertThat(((PiValidFieldMatch) piFieldMatch).isValid(), is(true));
+ break;
+ default:
+ Assert.assertTrue(false);
+ }
+ }
+ }
+
+ /**
+ * Reads in a criterion from the given resource and decodes it.
+ *
+ * @param resourceName resource to use to read the JSON for the rule
+ * @return decoded criterion
+ * @throws IOException if processing the resource fails
+ */
+ private Criterion getCriterion(String resourceName) throws IOException {
+ InputStream jsonStream = CriterionCodecTest.class.getResourceAsStream(resourceName);
+ JsonNode json = context.mapper().readTree(jsonStream);
+ MatcherAssert.assertThat(json, notNullValue());
+ Criterion criterion = criterionCodec.decode((ObjectNode) json, context);
+ Assert.assertThat(criterion, notNullValue());
+ return criterion;
+ }
}
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java b/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java
index 5e0b938..0b38729 100644
--- a/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java
+++ b/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java
@@ -15,6 +15,7 @@
*/
package org.onosproject.codec.impl;
+import java.util.Collection;
import java.util.Objects;
import org.hamcrest.Description;
@@ -43,6 +44,7 @@
import org.onosproject.net.flow.criteria.OchSignalTypeCriterion;
import org.onosproject.net.flow.criteria.OduSignalIdCriterion;
import org.onosproject.net.flow.criteria.OduSignalTypeCriterion;
+import org.onosproject.net.flow.criteria.PiCriterion;
import org.onosproject.net.flow.criteria.PortCriterion;
import org.onosproject.net.flow.criteria.SctpPortCriterion;
import org.onosproject.net.flow.criteria.TcpPortCriterion;
@@ -52,6 +54,14 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Joiner;
+import org.onosproject.net.pi.runtime.PiExactFieldMatch;
+import org.onosproject.net.pi.runtime.PiFieldMatch;
+import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
+import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+import org.onosproject.net.pi.runtime.PiValidFieldMatch;
+
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
/**
* Hamcrest matcher for criterion objects.
@@ -537,6 +547,101 @@
return true;
}
+ /**
+ * Matches a protocol-independent Type criterion object.
+ *
+ * @param criterion criterion to match
+ * @return true if the JSON matches the criterion, false otherwise.
+ */
+ private boolean matchCriterion(PiCriterion criterion) {
+ Collection<PiFieldMatch> piFieldMatches = criterion.fieldMatches();
+ JsonNode jsonMathes = jsonCriterion.get("matches");
+ if (!jsonMathes.isArray()) {
+ return false;
+ }
+ for (JsonNode matchNode : jsonMathes) {
+ for (PiFieldMatch fieldMatch : piFieldMatches) {
+
+ if (!Objects.equals(matchNode.get("field").textValue(), fieldMatch.fieldId().id())) {
+ description.appendText("match field was " + fieldMatch.fieldId().id());
+ return false;
+ }
+
+ if (!Objects.equals(matchNode.get("match").textValue(),
+ fieldMatch.type().name().toLowerCase())) {
+ description.appendText("match type was " + fieldMatch.type().name().toLowerCase());
+ return false;
+ }
+
+ switch (fieldMatch.type()) {
+ case EXACT:
+ if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("value")
+ .textValue(), null)),
+ ((PiExactFieldMatch) fieldMatch).value())) {
+ description.appendText("match value was " + ((PiExactFieldMatch) fieldMatch).value());
+ return false;
+ }
+ break;
+ case LPM:
+ if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("value")
+ .textValue(), null)),
+ ((PiLpmFieldMatch) fieldMatch).value())) {
+ description.appendText("match value was " + ((PiLpmFieldMatch) fieldMatch).value());
+ return false;
+ }
+ if (!Objects.equals(matchNode.get("prefixLength").intValue(),
+ ((PiLpmFieldMatch) fieldMatch).prefixLength())) {
+ description.appendText("match prefix was " +
+ ((PiLpmFieldMatch) fieldMatch).prefixLength());
+ return false;
+ }
+ break;
+ case TERNARY:
+ if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("value")
+ .textValue(), null)),
+ ((PiTernaryFieldMatch) fieldMatch).value())) {
+ description.appendText("match value was " + ((PiTernaryFieldMatch) fieldMatch).value());
+ return false;
+ }
+ if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("mask")
+ .textValue(), null)),
+ ((PiTernaryFieldMatch) fieldMatch).mask())) {
+ description.appendText("match mask was " + ((PiTernaryFieldMatch) fieldMatch).mask());
+ return false;
+ }
+ break;
+ case RANGE:
+ if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("highValue")
+ .textValue(), null)),
+ ((PiRangeFieldMatch) fieldMatch).highValue())) {
+ description.appendText("match high value was " +
+ ((PiRangeFieldMatch) fieldMatch).highValue());
+ return false;
+ }
+ if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("lowValue")
+ .textValue(), null)),
+ ((PiRangeFieldMatch) fieldMatch).lowValue())) {
+ description.appendText("match low value was " +
+ ((PiRangeFieldMatch) fieldMatch).lowValue());
+ return false;
+ }
+ break;
+ case VALID:
+ if (!Objects.equals(matchNode.get("value").asBoolean(),
+ ((PiValidFieldMatch) fieldMatch).isValid())) {
+ description.appendText("match value was " + ((PiValidFieldMatch) fieldMatch).isValid());
+ return false;
+ }
+ break;
+ default:
+ description.appendText("match type was " + fieldMatch.type().name().toLowerCase());
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
@Override
public boolean matchesSafely(JsonNode jsonCriterion,
@@ -641,6 +746,8 @@
case ODU_SIGTYPE:
return matchCriterion((OduSignalTypeCriterion) criterion);
+ case PROTOCOL_INDEPENDENT:
+ return matchCriterion((PiCriterion) criterion);
default:
// Don't know how to format this type
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java
index 307c524..832a420 100644
--- a/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java
+++ b/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java
@@ -15,7 +15,10 @@
*/
package org.onosproject.codec.impl;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.hamcrest.MatcherAssert;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.Ip4Address;
@@ -23,6 +26,7 @@
import org.onlab.packet.MacAddress;
import org.onlab.packet.MplsLabel;
import org.onlab.packet.VlanId;
+import org.onlab.util.ImmutableByteSequence;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.net.ChannelSpacing;
@@ -36,9 +40,23 @@
import org.onosproject.net.flow.instructions.L1ModificationInstruction;
import org.onosproject.net.flow.instructions.L2ModificationInstruction;
import org.onosproject.net.flow.instructions.L3ModificationInstruction;
+import org.onosproject.net.flow.instructions.PiInstruction;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiTableAction;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
import static org.onosproject.codec.impl.InstructionJsonMatcher.matchesInstruction;
/**
@@ -232,4 +250,77 @@
assertThat(instructionJson, matchesInstruction(instruction));
}
+ /**
+ * Tests the encoding of protocol-independent instructions.
+ */
+ @Test
+ public void piInstructionEncodingTest() {
+ PiActionId actionId = PiActionId.of("set_egress_port");
+ PiActionParamId actionParamId = PiActionParamId.of("port");
+ PiActionParam actionParam = new PiActionParam(actionParamId, ImmutableByteSequence.copyFrom(10));
+ PiTableAction action = PiAction.builder().withId(actionId).withParameter(actionParam).build();
+ final PiInstruction actionInstruction = Instructions.piTableAction(action);
+ final ObjectNode actionInstructionJson =
+ instructionCodec.encode(actionInstruction, context);
+ assertThat(actionInstructionJson, matchesInstruction(actionInstruction));
+
+ PiTableAction actionGroupId = PiActionGroupId.of(10);
+ final PiInstruction actionGroupIdInstruction = Instructions.piTableAction(actionGroupId);
+ final ObjectNode actionGroupIdInstructionJson =
+ instructionCodec.encode(actionGroupIdInstruction, context);
+ assertThat(actionGroupIdInstructionJson, matchesInstruction(actionGroupIdInstruction));
+
+ PiTableAction actionGroupMemberId = PiActionGroupMemberId.of(10);
+ final PiInstruction actionGroupMemberIdInstruction = Instructions.piTableAction(actionGroupMemberId);
+ final ObjectNode actionGroupMemberIdInstructionJson =
+ instructionCodec.encode(actionGroupMemberIdInstruction, context);
+ assertThat(actionGroupMemberIdInstructionJson, matchesInstruction(actionGroupMemberIdInstruction));
+ }
+
+ /**
+ * Tests the decoding of protocol-independent instructions.
+ */
+ @Test
+ public void piInstructionDecodingTest() throws IOException {
+
+ Instruction actionInstruction = getInstruction("PiActionInstruction.json");
+ Assert.assertThat(actionInstruction.type(), is(Instruction.Type.PROTOCOL_INDEPENDENT));
+ PiTableAction action = ((PiInstruction) actionInstruction).action();
+ Assert.assertThat(action.type(), is(PiTableAction.Type.ACTION));
+ Assert.assertThat(((PiAction) action).id().id(), is("set_egress_port"));
+ Assert.assertThat(((PiAction) action).parameters().size(), is(1));
+ Collection<PiActionParam> actionParams = ((PiAction) action).parameters();
+ PiActionParam actionParam = actionParams.iterator().next();
+ Assert.assertThat(actionParam.id().id(), is("port"));
+ Assert.assertThat(actionParam.value(), is(copyFrom((byte) 0x1)));
+
+ Instruction actionGroupIdInstruction = getInstruction("PiActionGroupIdInstruction.json");
+ Assert.assertThat(actionInstruction.type(), is(Instruction.Type.PROTOCOL_INDEPENDENT));
+ PiTableAction actionGroupId = ((PiInstruction) actionGroupIdInstruction).action();
+ Assert.assertThat(actionGroupId.type(), is(PiTableAction.Type.ACTION_GROUP_ID));
+ Assert.assertThat(((PiActionGroupId) actionGroupId).id(), is(100));
+
+ Instruction actionMemberIdInstruction = getInstruction("PiActionMemberIdInstruction.json");
+ Assert.assertThat(actionInstruction.type(), is(Instruction.Type.PROTOCOL_INDEPENDENT));
+ PiTableAction actionMemberId = ((PiInstruction) actionMemberIdInstruction).action();
+ Assert.assertThat(actionMemberId.type(), is(PiTableAction.Type.GROUP_MEMBER_ID));
+ Assert.assertThat(((PiActionGroupMemberId) actionMemberId).id(), is(100));
+ }
+
+ /**
+ * Reads in an instruction from the given resource and decodes it.
+ *
+ * @param resourceName resource to use to read the JSON for the rule
+ * @return decoded instruction
+ * @throws IOException if processing the resource fails
+ */
+ private Instruction getInstruction(String resourceName) throws IOException {
+ InputStream jsonStream = InstructionCodecTest.class.getResourceAsStream(resourceName);
+ JsonNode json = context.mapper().readTree(jsonStream);
+ MatcherAssert.assertThat(json, notNullValue());
+ Instruction instruction = instructionCodec.decode((ObjectNode) json, context);
+ Assert.assertThat(instruction, notNullValue());
+ return instruction;
+ }
+
}
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java b/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java
index 94278ff..9911a4f 100644
--- a/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java
+++ b/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java
@@ -35,6 +35,16 @@
import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanPcpInstruction;
import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModIPInstruction;
import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModIPv6FlowLabelInstruction;
+import org.onosproject.net.flow.instructions.PiInstruction;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+
+import java.util.Collection;
+import java.util.Objects;
+
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
/**
* Hamcrest matcher for instructions.
@@ -503,6 +513,71 @@
return true;
}
+ /**
+ * Matches the contents of a protocol-independent instruction.
+ *
+ * @param instructionJson JSON instruction to match
+ * @param description Description object used for recording errors
+ * @return true if contents match, false otherwise
+ */
+ private boolean matchPiInstruction(JsonNode instructionJson,
+ Description description) {
+ PiInstruction instructionToMatch = (PiInstruction) instruction;
+
+ final String jsonSubtype = instructionJson.get("subtype").textValue();
+ if (!instructionToMatch.action().type().name().equals(jsonSubtype)) {
+ description.appendText("subtype was " + jsonSubtype);
+ return false;
+ }
+
+ final String jsonType = instructionJson.get("type").textValue();
+ if (!instructionToMatch.type().name().equals(jsonType)) {
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ switch (instructionToMatch.action().type()) {
+ case ACTION:
+ if (!Objects.equals(instructionJson.get("actionId").textValue(),
+ ((PiAction) instructionToMatch.action()).id().id())) {
+ description.appendText("action was " + ((PiAction) instructionToMatch.action()).id().id());
+ return false;
+ }
+ Collection<PiActionParam> piActionParams = ((PiAction) instructionToMatch.action()).parameters();
+ JsonNode jsonParams = instructionJson.get("actionParams");
+ for (PiActionParam actionParam : piActionParams) {
+ if (!Objects.equals(copyFrom(HexString.fromHexString(jsonParams.get(actionParam.id().id())
+ .textValue(), null)),
+ actionParam.value())) {
+ description.appendText("action param value was " + actionParam.value());
+ return false;
+ }
+ }
+ break;
+ case ACTION_GROUP_ID:
+ if (!Objects.equals(instructionJson.get("groupId").asInt(),
+ ((PiActionGroupId) instructionToMatch.action()).id())) {
+ description.appendText("action group id was " +
+ ((PiActionGroupId) instructionToMatch.action()).id());
+ return false;
+ }
+ break;
+ case GROUP_MEMBER_ID:
+ if (!Objects.equals(instructionJson.get("memberId").asInt(),
+ ((PiActionGroupMemberId) instructionToMatch.action()).id())) {
+ description.appendText("action member id was " +
+ ((PiActionGroupMemberId) instructionToMatch.action()).id());
+ return false;
+ }
+ break;
+ default:
+ description.appendText("type was " + jsonType);
+ return false;
+ }
+
+ return true;
+ }
+
@Override
public boolean matchesSafely(JsonNode jsonInstruction, Description description) {
@@ -541,6 +616,8 @@
return matchModMplsLabelInstruction(jsonInstruction, description);
} else if (instruction instanceof ModOduSignalIdInstruction) {
return matchModOduSingalIdInstruction(jsonInstruction, description);
+ } else if (instruction instanceof PiInstruction) {
+ return matchPiInstruction(jsonInstruction, description);
} else if (instruction instanceof NoActionInstruction) {
return true;
}