Various improvements to PI group handling
- Moved group translation logic to core service
- Removed dependency on KRYO
- Fixed bug where tratments with PI instructions where not supported if
an interpreter was present
- Fixed bug where action profile name was not found during protobuf
encoding (always perform P4Info lookup by name and alias)
- Improved reading of members by issuing one big request for all
groups
Change-Id: Ifcf8380b09293e70be15cf4999bd2845caf5d01e
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiFlowRuleTranslationService.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiFlowRuleTranslationService.java
deleted file mode 100644
index 4948ebb..0000000
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiFlowRuleTranslationService.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2017-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.net.pi.runtime;
-
-import com.google.common.annotations.Beta;
-import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.pi.model.PiPipeconf;
-
-/**
- * A service to translate ONOS flow rules to table entries of a protocol-independent pipeline.
- */
-@Beta
-public interface PiFlowRuleTranslationService {
-
- /**
- * Returns a table entry equivalent to the given flow rule for the given protocol-independent
- * pipeline configuration.
- *
- * @param rule a flow rule
- * @param pipeconf a pipeline configuration
- * @return a table entry
- * @throws PiFlowRuleTranslationException if the flow rule cannot be translated
- */
- PiTableEntry translate(FlowRule rule, PiPipeconf pipeconf)
- throws PiFlowRuleTranslationException;
-
- /**
- * Signals that an error was encountered while translating flow rule.
- */
- class PiFlowRuleTranslationException extends Exception {
-
- /**
- * Creates a new exception with the given message.
- *
- * @param message a message
- */
- public PiFlowRuleTranslationException(String message) {
- super(message);
- }
- }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiGroupKey.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiGroupKey.java
new file mode 100644
index 0000000..598f3e7
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiGroupKey.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2017-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.net.pi.runtime;
+
+import com.google.common.base.Objects;
+import org.onosproject.net.group.GroupKey;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of GroupKey for the case of a protocol-independent pipeline.
+ */
+public final class PiGroupKey implements GroupKey {
+
+ private final PiTableId tableId;
+ private final PiActionProfileId piActionProfileId;
+ private final int groupId;
+
+ /**
+ * Returns a new group key for the given table ID, action profile ID, and group ID.
+ *
+ * @param tableId table ID
+ * @param piActionProfileId action profile ID
+ * @param groupId group ID
+ */
+ public PiGroupKey(PiTableId tableId, PiActionProfileId piActionProfileId, int groupId) {
+ this.tableId = checkNotNull(tableId);
+ this.piActionProfileId = checkNotNull(piActionProfileId);
+ this.groupId = groupId;
+ }
+
+ /**
+ * Returns the table ID defined by this key.
+ *
+ * @return table ID
+ */
+ public PiTableId tableId() {
+ return tableId;
+ }
+
+ /**
+ * Returns the group ID defined by this key.
+ *
+ * @return group ID
+ */
+ public int groupId() {
+ return groupId;
+ }
+
+ /**
+ * Returns the action profile ID defined by this key.
+ *
+ * @return action profile ID
+ */
+ public PiActionProfileId actionProfileId() {
+ return piActionProfileId;
+ }
+
+ @Override
+ public byte[] key() {
+ return toString().getBytes();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof PiGroupKey)) {
+ return false;
+ }
+ PiGroupKey that = (PiGroupKey) o;
+ return groupId == that.groupId &&
+ Objects.equal(tableId, that.tableId) &&
+ Objects.equal(piActionProfileId, that.piActionProfileId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(tableId, piActionProfileId, groupId);
+ }
+
+ @Override
+ public String toString() {
+ return tableId.id() + "-" + piActionProfileId.id() + "-" + String.valueOf(groupId);
+ }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTranslationService.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTranslationService.java
new file mode 100644
index 0000000..a0c1101
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTranslationService.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017-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.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.pi.model.PiPipeconf;
+
+/**
+ * A service to translate ONOS entities to protocol-independent ones.
+ */
+@Beta
+public interface PiTranslationService {
+
+ /**
+ * Returns a PI table entry equivalent to the given flow rule for the given protocol-independent pipeline
+ * configuration.
+ *
+ * @param rule a flow rule
+ * @param pipeconf a pipeline configuration
+ * @return a table entry
+ * @throws PiTranslationException if the flow rule cannot be translated
+ */
+ PiTableEntry translateFlowRule(FlowRule rule, PiPipeconf pipeconf)
+ throws PiTranslationException;
+
+ /**
+ * Returns a PI action group equivalent to the given group for the given protocol-independent pipeline
+ * configuration.
+ *
+ * @param group a group
+ * @param pipeconf a pipeline configuration
+ * @return a PI action group
+ * @throws PiTranslationException if the group cannot be translated
+ */
+ PiActionGroup translateGroup(Group group, PiPipeconf pipeconf)
+ throws PiTranslationException;
+
+ /**
+ * Signals that an error was encountered while translating an entity.
+ */
+ class PiTranslationException extends Exception {
+
+ /**
+ * Creates a new exception with the given message.
+ *
+ * @param message a message
+ */
+ public PiTranslationException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/CriterionTranslatorHelper.java b/core/net/src/main/java/org/onosproject/net/pi/impl/CriterionTranslatorHelper.java
index df73a65..4e0fd16 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/CriterionTranslatorHelper.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/CriterionTranslatorHelper.java
@@ -92,7 +92,7 @@
import static java.lang.String.format;
import static org.onlab.util.ImmutableByteSequence.ByteSequenceTrimException;
import static org.onosproject.net.pi.impl.CriterionTranslator.CriterionTranslatorException;
-import static org.onosproject.net.pi.runtime.PiFlowRuleTranslationService.PiFlowRuleTranslationException;
+import static org.onosproject.net.pi.runtime.PiTranslationService.PiTranslationException;
/**
* Helper class to translate criterion instances to PI field matches.
@@ -145,14 +145,14 @@
* @param matchType match type
* @param bitWidth size of the field match in bits
* @return a PI field match
- * @throws PiFlowRuleTranslationException if the criterion cannot be translated (see exception message)
+ * @throws PiTranslationException if the criterion cannot be translated (see exception message)
*/
static PiFieldMatch translateCriterion(Criterion criterion, PiHeaderFieldId fieldId, PiMatchType matchType,
int bitWidth)
- throws PiFlowRuleTranslationException {
+ throws PiTranslationException {
if (!TRANSLATORS.containsKey(criterion.getClass())) {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"Translation of criterion class %s is not implemented.",
criterion.getClass().getSimpleName()));
}
@@ -171,15 +171,15 @@
Pair<ImmutableByteSequence, Integer> lp = translator.lpmMatch();
return new PiLpmFieldMatch(fieldId, lp.getLeft(), lp.getRight());
default:
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"Translation of criterion %s (%s class) to match type %s is not implemented.",
criterion.type().name(), criterion.getClass().getSimpleName(), matchType.name()));
}
} catch (ByteSequenceTrimException e) {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"Size mismatch for criterion %s: %s", criterion.type(), e.getMessage()));
} catch (CriterionTranslatorException e) {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"Unable to translate criterion %s: %s", criterion.type(), e.getMessage()));
}
}
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java
index fbfd9b7..c0fd846 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java
@@ -21,7 +21,6 @@
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.net.Device;
import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.IndexTableId;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
@@ -61,7 +60,9 @@
import static org.onlab.util.ImmutableByteSequence.fit;
import static org.onosproject.net.flow.criteria.Criterion.Type.PROTOCOL_INDEPENDENT;
import static org.onosproject.net.pi.impl.CriterionTranslatorHelper.translateCriterion;
-import static org.onosproject.net.pi.runtime.PiFlowRuleTranslationService.PiFlowRuleTranslationException;
+import static org.onosproject.net.pi.impl.PiUtils.getInterpreterOrNull;
+import static org.onosproject.net.pi.impl.PiUtils.translateTableId;
+import static org.onosproject.net.pi.runtime.PiTranslationService.PiTranslationException;
/**
* Implementation of flow rule translation logic.
@@ -69,77 +70,43 @@
final class PiFlowRuleTranslator {
public static final int MAX_PI_PRIORITY = (int) Math.pow(2, 24);
- private static final Logger log = LoggerFactory.getLogger(PiFlowRuleTranslationServiceImpl.class);
+ private static final Logger log = LoggerFactory.getLogger(PiFlowRuleTranslator.class);
private PiFlowRuleTranslator() {
// Hide constructor.
}
- static PiTableEntry translateFlowRule(FlowRule rule, PiPipeconf pipeconf, Device device)
- throws PiFlowRuleTranslationException {
+ /**
+ * Returns a PI table entry equivalent to the given flow rule, for the given pipeconf and device.
+ *
+ * @param rule flow rule
+ * @param pipeconf pipeconf
+ * @param device device
+ * @return PI table entry
+ * @throws PiTranslationException if the flow rule cannot be translated
+ */
+ static PiTableEntry translate(FlowRule rule, PiPipeconf pipeconf, Device device)
+ throws PiTranslationException {
PiPipelineModel pipelineModel = pipeconf.pipelineModel();
// Retrieve interpreter, if any.
- final PiPipelineInterpreter interpreter;
+ final PiPipelineInterpreter interpreter = getInterpreterOrNull(device, pipeconf);
+ // Get table model.
+ final PiTableId piTableId = translateTableId(rule.table(), interpreter);
+ final PiTableModel tableModel = getTableModel(piTableId, pipelineModel);
+ // Translate selector.
+ final Collection<PiFieldMatch> fieldMatches = translateFieldMatches(interpreter, rule.selector(), tableModel);
+ // Translate treatment.
+ final PiTableAction piTableAction = translateTreatment(rule.treatment(), interpreter, piTableId, pipelineModel);
- if (device != null) {
- interpreter = device.is(PiPipelineInterpreter.class) ? device.as(PiPipelineInterpreter.class) : null;
- } else {
- // The case of device == null should be admitted only during unit testing.
- // In any other case, the interpreter should be constructed using the device.as() method to make sure that
- // behaviour's handler/data attributes are correctly populated.
- // FIXME: modify test class PiFlowRuleTranslatorTest to avoid passing null device
- // I.e. we need to create a device object that supports is/as method for obtaining the interpreter.
- log.warn("translateFlowRule() called with device == null, is this a unit test?");
- try {
- interpreter = (PiPipelineInterpreter) pipeconf.implementation(PiPipelineInterpreter.class)
- .orElse(null)
- .newInstance();
- } catch (InstantiationException | IllegalAccessException e) {
- throw new RuntimeException(format("Unable to instantiate interpreter of pipeconf %s", pipeconf.id()));
- }
- }
-
- PiTableId piTableId;
- switch (rule.table().type()) {
- case PIPELINE_INDEPENDENT:
- piTableId = (PiTableId) rule.table();
- break;
- case INDEX:
- IndexTableId indexId = (IndexTableId) rule.table();
- if (interpreter == null) {
- throw new PiFlowRuleTranslationException(format(
- "Unable to map table ID '%d' from index to PI: missing interpreter", indexId.id()));
- } else if (!interpreter.mapFlowRuleTableId(indexId.id()).isPresent()) {
- throw new PiFlowRuleTranslationException(format(
- "Unable to map table ID '%d' from index to PI: missing ID in interpreter", indexId.id()));
- } else {
- piTableId = interpreter.mapFlowRuleTableId(indexId.id()).get();
- }
- break;
- default:
- throw new PiFlowRuleTranslationException(format(
- "Unrecognized table ID type %s", rule.table().type().name()));
- }
-
- PiTableModel table = pipelineModel.table(piTableId.toString())
- .orElseThrow(() -> new PiFlowRuleTranslationException(format(
- "Not such a table in pipeline model: %s", piTableId)));
-
- /* Translate selector */
- Collection<PiFieldMatch> fieldMatches = buildFieldMatches(interpreter, rule.selector(), table);
-
- /* Translate treatment */
- PiTableAction piTableAction = buildAction(rule.treatment(), interpreter, piTableId);
- piTableAction = typeCheckAction(piTableAction, table);
-
- PiTableEntry.Builder tableEntryBuilder = PiTableEntry.builder();
+ // Build PI entry.
+ final PiTableEntry.Builder tableEntryBuilder = PiTableEntry.builder();
// In the P4 world 0 is the highest priority, in ONOS the lowest one.
// FIXME: move priority conversion to the driver, where different constraints might apply
// e.g. less bits for encoding priority in TCAM-based implementations.
- int newPriority;
+ final int newPriority;
if (rule.priority() > MAX_PI_PRIORITY) {
log.warn("Flow rule priority too big, setting translated priority to max value {}: {}",
MAX_PI_PRIORITY, rule);
@@ -157,11 +124,11 @@
.withAction(piTableAction);
if (!rule.isPermanent()) {
- if (table.supportsAging()) {
+ if (tableModel.supportsAging()) {
tableEntryBuilder.withTimeout((double) rule.timeout());
} else {
log.warn("Flow rule is temporary, but table '{}' doesn't support " +
- "aging, translating to permanent.", table.name());
+ "aging, translating to permanent.", tableModel.name());
}
}
@@ -169,12 +136,40 @@
return tableEntryBuilder.build();
}
+
+ /**
+ * Returns a PI action equivalent to the given treatment, optionally using the given interpreter. This method also
+ * checks that the produced PI table action is suitable for the given table ID and pipeline model. If suitable, the
+ * returned action instance will have parameters well-sized, according to the table model.
+ *
+ * @param treatment traffic treatment
+ * @param interpreter interpreter
+ * @param tableId PI table ID
+ * @param pipelineModel pipeline model
+ * @return PI table action
+ * @throws PiTranslationException if the treatment cannot be translated or if the PI action is not suitable for the
+ * given pipeline model
+ */
+ static PiTableAction translateTreatment(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
+ PiTableId tableId, PiPipelineModel pipelineModel)
+ throws PiTranslationException {
+ PiTableModel tableModel = getTableModel(tableId, pipelineModel);
+ return typeCheckAction(buildAction(treatment, interpreter, tableId), tableModel);
+ }
+
+ private static PiTableModel getTableModel(PiTableId piTableId, PiPipelineModel pipelineModel)
+ throws PiTranslationException {
+ return pipelineModel.table(piTableId.toString())
+ .orElseThrow(() -> new PiTranslationException(format(
+ "Not such a table in pipeline model: %s", piTableId)));
+ }
+
/**
* Builds a PI action out of the given treatment, optionally using the given interpreter.
*/
private static PiTableAction buildAction(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
- PiTableId tableId)
- throws PiFlowRuleTranslationException {
+ PiTableId tableId)
+ throws PiTranslationException {
PiTableAction piTableAction = null;
@@ -184,7 +179,7 @@
if (treatment.allInstructions().size() == 1) {
piTableAction = ((PiInstruction) inst).action();
} else {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"Unable to translate treatment, found multiple instructions " +
"of which one is protocol-independent: %s", treatment));
}
@@ -196,14 +191,14 @@
try {
piTableAction = interpreter.mapTreatment(treatment, tableId);
} catch (PiPipelineInterpreter.PiInterpreterException e) {
- throw new PiFlowRuleTranslationException(
+ throw new PiTranslationException(
"Interpreter was unable to translate treatment. " + e.getMessage());
}
}
if (piTableAction == null) {
// No PiInstruction, no interpreter. It's time to give up.
- throw new PiFlowRuleTranslationException(
+ throw new PiTranslationException(
"Unable to translate treatment, neither an interpreter or a "
+ "protocol-independent instruction were provided.");
}
@@ -211,13 +206,8 @@
return piTableAction;
}
- /**
- * Checks that the given PI table action is suitable for the given table
- * model and returns a new action instance with parameters well-sized,
- * according to the table model. If not suitable, throws an exception explaining why.
- */
private static PiTableAction typeCheckAction(PiTableAction piTableAction, PiTableModel table)
- throws PiFlowRuleTranslationException {
+ throws PiTranslationException {
switch (piTableAction.type()) {
case ACTION:
return checkPiAction((PiAction) piTableAction, table);
@@ -229,15 +219,15 @@
}
private static PiTableAction checkPiAction(PiAction piAction, PiTableModel table)
- throws PiFlowRuleTranslationException {
+ throws PiTranslationException {
// Table supports this action?
PiActionModel actionModel = table.action(piAction.id().name()).orElseThrow(
- () -> new PiFlowRuleTranslationException(format("Not such action '%s' for table '%s'",
- piAction.id(), table.name())));
+ () -> new PiTranslationException(format("Not such action '%s' for table '%s'",
+ piAction.id(), table.name())));
// Is the number of runtime parameters correct?
if (actionModel.params().size() != piAction.parameters().size()) {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"Wrong number of runtime parameters for action '%s', expected %d but found %d",
actionModel.name(), actionModel.params().size(), piAction.parameters().size()));
}
@@ -247,13 +237,13 @@
PiAction.Builder newActionBuilder = PiAction.builder().withId(piAction.id());
for (PiActionParam param : piAction.parameters()) {
PiActionParamModel paramModel = actionModel.param(param.id().name())
- .orElseThrow(() -> new PiFlowRuleTranslationException(format(
+ .orElseThrow(() -> new PiTranslationException(format(
"Not such parameter '%s' for action '%s'", param.id(), actionModel.name())));
try {
newActionBuilder.withParameter(new PiActionParam(param.id(),
fit(param.value(), paramModel.bitWidth())));
} catch (ByteSequenceTrimException e) {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"Size mismatch for parameter '%s' of action '%s': %s",
param.id(), piAction.id(), e.getMessage()));
}
@@ -266,9 +256,9 @@
* Builds a collection of PI field matches out of the given selector, optionally using the given interpreter. The
* field matches returned are guaranteed to be compatible for the given table model.
*/
- private static Collection<PiFieldMatch> buildFieldMatches(PiPipelineInterpreter interpreter,
- TrafficSelector selector, PiTableModel tableModel)
- throws PiFlowRuleTranslationException {
+ private static Collection<PiFieldMatch> translateFieldMatches(PiPipelineInterpreter interpreter,
+ TrafficSelector selector, PiTableModel tableModel)
+ throws PiTranslationException {
Map<PiHeaderFieldId, PiFieldMatch> fieldMatches = Maps.newHashMap();
@@ -325,7 +315,7 @@
break;
// FIXME: Can we handle the case of RANGE or VALID match?
default:
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"No value found for required match field '%s'", fieldId));
}
// Next field.
@@ -339,7 +329,7 @@
try {
fieldMatch = translateCriterion(criterion, fieldId, fieldModel.matchType(), bitWidth);
translatedCriteria.add(criterion);
- } catch (PiFlowRuleTranslationException ex) {
+ } catch (PiTranslationException ex) {
// Ignore exception if the same field was found in PiCriterion.
if (piCriterionFields.containsKey(fieldId)) {
ignoredCriteria.add(criterion);
@@ -355,7 +345,7 @@
// Field was already translated from other criterion.
// Throw exception only if we are trying to match on different values of the same field...
if (!fieldMatch.equals(piCriterionFields.get(fieldId))) {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"Duplicate match field '%s': instance translated from criterion '%s' is different to " +
"what found in PiCriterion.", fieldId, criterion.type()));
}
@@ -377,7 +367,7 @@
.filter(c -> !translatedCriteria.contains(c) && !ignoredCriteria.contains(c))
.forEach(c -> skippedCriteriaJoiner.add(c.type().name()));
if (skippedCriteriaJoiner.length() > 0) {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"The following criteria cannot be translated for table '%s': %s",
tableModel.name(), skippedCriteriaJoiner.toString()));
}
@@ -388,7 +378,7 @@
.filter(k -> !usedPiCriterionFields.contains(k) && !ignoredPiCriterionFields.contains(k))
.forEach(k -> skippedPiFieldsJoiner.add(k.id()));
if (skippedPiFieldsJoiner.length() > 0) {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"The following PiCriterion field matches are not supported in table '%s': %s",
tableModel.name(), skippedPiFieldsJoiner.toString()));
}
@@ -397,11 +387,11 @@
}
private static PiFieldMatch typeCheckFieldMatch(PiFieldMatch fieldMatch, PiTableMatchFieldModel fieldModel)
- throws PiFlowRuleTranslationException {
+ throws PiTranslationException {
// Check parameter type and size
if (!fieldModel.matchType().equals(fieldMatch.type())) {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"Wrong match type for field '%s', expected %s, but found %s",
fieldMatch.fieldId(), fieldModel.matchType().name(), fieldMatch.type().name()));
}
@@ -427,7 +417,7 @@
case LPM:
PiLpmFieldMatch lpmfield = (PiLpmFieldMatch) fieldMatch;
if (lpmfield.prefixLength() > modelBitWidth) {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"Invalid prefix length for LPM field '%s', found %d but field has bit-width %d",
fieldMatch.fieldId(), lpmfield.prefixLength(), modelBitWidth));
}
@@ -446,7 +436,7 @@
"Unrecognized match type " + fieldModel.matchType().name());
}
} catch (ByteSequenceTrimException e) {
- throw new PiFlowRuleTranslationException(format(
+ throw new PiTranslationException(format(
"Size mismatch for field %s: %s", fieldMatch.fieldId(), e.getMessage()));
}
}
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiGroupTranslator.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiGroupTranslator.java
new file mode 100644
index 0000000..7b798c0
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiGroupTranslator.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2017-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.net.pi.impl;
+
+import org.onosproject.net.Device;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+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.PiActionGroup;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import org.onosproject.net.pi.runtime.PiActionGroupMember;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import org.onosproject.net.pi.runtime.PiGroupKey;
+import org.onosproject.net.pi.runtime.PiTableAction;
+import org.onosproject.net.pi.runtime.PiTranslationService.PiTranslationException;
+
+import java.nio.ByteBuffer;
+
+import static java.lang.String.format;
+import static org.onosproject.net.pi.impl.PiFlowRuleTranslator.translateTreatment;
+import static org.onosproject.net.pi.impl.PiUtils.getInterpreterOrNull;
+import static org.onosproject.net.pi.runtime.PiTableAction.Type.ACTION;
+
+/**
+ * Implementation of group translation logic.
+ */
+final class PiGroupTranslator {
+
+ private PiGroupTranslator() {
+ // Hides constructor.
+ }
+
+ /**
+ * Returns a PI action group equivalent to the given group, for the given pipeconf and device.
+ *
+ * @param group group
+ * @param pipeconf pipeconf
+ * @param device device
+ * @return PI action group
+ * @throws PiTranslationException if the group cannot be translated
+ */
+ static PiActionGroup translate(Group group, PiPipeconf pipeconf, Device device) throws PiTranslationException {
+
+ final PiPipelineInterpreter interpreter = getInterpreterOrNull(device, pipeconf);
+
+ final PiActionGroup.Builder piActionGroupBuilder = PiActionGroup.builder()
+ .withId(PiActionGroupId.of(group.id().id()));
+
+ switch (group.type()) {
+ case SELECT:
+ piActionGroupBuilder.withType(PiActionGroup.Type.SELECT);
+ break;
+ default:
+ throw new PiTranslationException(format("Group type %s not supported", group.type()));
+ }
+
+ if (!(group.appCookie() instanceof PiGroupKey)) {
+ throw new PiTranslationException("Group app cookie is not PI (class should be PiGroupKey)");
+ }
+ final PiGroupKey groupKey = (PiGroupKey) group.appCookie();
+
+ piActionGroupBuilder.withActionProfileId(groupKey.actionProfileId());
+
+ // Translate group buckets to PI group members
+ short bucketIdx = 0;
+ for (GroupBucket bucket : group.buckets().buckets()) {
+ /*
+ FIXME: the way member IDs are computed can cause collisions!
+ Problem:
+ In P4Runtime action group members, i.e. action buckets, are associated to a numeric ID chosen
+ at member insertion time. This ID must be unique for the whole action profile (i.e. the group table in
+ OpenFlow). In ONOS, GroupBucket doesn't specify any ID.
+
+ Solutions:
+ - Change GroupBucket API to force application wanting to perform group operations to specify a member id.
+ - Maintain state to dynamically allocate/deallocate member IDs, e.g. in a dedicated service, or in a
+ P4Runtime Group Provider.
+
+ Hack:
+ Statically derive member ID by combining groupId and position of the bucket in the list.
+ */
+ ByteBuffer bb = ByteBuffer.allocate(4)
+ .putShort((short) (group.id().id() & 0xffff))
+ .putShort(bucketIdx);
+ bb.rewind();
+ int memberId = bb.getInt();
+ bucketIdx++;
+
+ final PiTableAction tableAction = translateTreatment(bucket.treatment(), interpreter, groupKey.tableId(),
+ pipeconf.pipelineModel());
+
+ if (tableAction.type() != ACTION) {
+ throw new PiTranslationException(format(
+ "PI table action of type %s is not supported in groups", tableAction.type()));
+ }
+
+ piActionGroupBuilder.addMember(PiActionGroupMember.builder()
+ .withId(PiActionGroupMemberId.of(memberId))
+ .withAction((PiAction) tableAction)
+ .withWeight(bucket.weight())
+ .build());
+ }
+
+ return piActionGroupBuilder.build();
+ }
+}
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslationServiceImpl.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
similarity index 62%
rename from core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslationServiceImpl.java
rename to core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
index c55a57c..68a2d20 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslationServiceImpl.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
@@ -23,26 +23,27 @@
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.group.Group;
import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.net.pi.runtime.PiFlowRuleTranslationService;
+import org.onosproject.net.pi.runtime.PiActionGroup;
import org.onosproject.net.pi.runtime.PiTableEntry;
+import org.onosproject.net.pi.runtime.PiTranslationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import static org.onosproject.net.pi.impl.PiFlowRuleTranslator.translateFlowRule;
-
/**
- * Implementation of the protocol-independent flow rule translation service.
+ * Implementation of the protocol-independent translation service.
*/
@Component(immediate = true)
@Service
-public class PiFlowRuleTranslationServiceImpl implements PiFlowRuleTranslationService {
+public class PiTranslationServiceImpl implements PiTranslationService {
private final Logger log = LoggerFactory.getLogger(this.getClass());
- // TODO: implement cache to speed up translation of flow rules.
+ // TODO: implement cache to speed up translation.
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@@ -58,15 +59,21 @@
}
@Override
- public PiTableEntry translate(FlowRule rule, PiPipeconf pipeconf)
- throws PiFlowRuleTranslationException {
+ public PiTableEntry translateFlowRule(FlowRule rule, PiPipeconf pipeconf) throws PiTranslationException {
+ return PiFlowRuleTranslator.translate(rule, pipeconf, getDevice(rule.deviceId()));
+ }
- final Device device = deviceService.getDevice(rule.deviceId());
+ @Override
+ public PiActionGroup translateGroup(Group group, PiPipeconf pipeconf) throws PiTranslationException {
+ return PiGroupTranslator.translate(group, pipeconf, getDevice(group.deviceId()));
+ }
+
+ private Device getDevice(DeviceId deviceId) throws PiTranslationException {
+ final Device device = deviceService.getDevice(deviceId);
if (device == null) {
- throw new PiFlowRuleTranslationException("Unable to get device " + rule.deviceId());
+ throw new PiTranslationException("Unable to get device " + deviceId);
}
-
- return translateFlowRule(rule, pipeconf, device);
+ return device;
}
}
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiUtils.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiUtils.java
new file mode 100644
index 0000000..aab8b40
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiUtils.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017-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.net.pi.impl;
+
+import org.onosproject.net.Device;
+import org.onosproject.net.flow.IndexTableId;
+import org.onosproject.net.flow.TableId;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.runtime.PiTranslationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static java.lang.String.format;
+
+/**
+ * PI utility class.
+ */
+final class PiUtils {
+
+ private static final Logger log = LoggerFactory.getLogger(PiUtils.class);
+
+ private PiUtils() {
+ // Hides constructor.
+ }
+
+ static PiPipelineInterpreter getInterpreterOrNull(Device device, PiPipeconf pipeconf) {
+ if (device != null) {
+ return device.is(PiPipelineInterpreter.class) ? device.as(PiPipelineInterpreter.class) : null;
+ } else {
+ // The case of device == null should be admitted only during unit testing.
+ // In any other case, the interpreter should be constructed using the device.as() method to make sure that
+ // behaviour's handler/data attributes are correctly populated.
+ // FIXME: modify test class PiFlowRuleTranslatorTest to avoid passing null device
+ // I.e. we need to create a device object that supports is/as method for obtaining the interpreter.
+ log.warn("getInterpreterOrNull() called with device == null, is this a unit test?");
+ try {
+ return (PiPipelineInterpreter) pipeconf.implementation(PiPipelineInterpreter.class)
+ .orElse(null)
+ .newInstance();
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw new RuntimeException(format("Unable to instantiate interpreter of pipeconf %s", pipeconf.id()));
+ }
+ }
+ }
+
+ static PiTableId translateTableId(TableId tableId, PiPipelineInterpreter interpreter)
+ throws PiTranslationService.PiTranslationException {
+ switch (tableId.type()) {
+ case PIPELINE_INDEPENDENT:
+ return (PiTableId) tableId;
+ case INDEX:
+ IndexTableId indexId = (IndexTableId) tableId;
+ if (interpreter == null) {
+ throw new PiTranslationService.PiTranslationException(format(
+ "Unable to map table ID '%d' from index to PI: missing interpreter", indexId.id()));
+ } else if (!interpreter.mapFlowRuleTableId(indexId.id()).isPresent()) {
+ throw new PiTranslationService.PiTranslationException(format(
+ "Unable to map table ID '%d' from index to PI: missing ID in interpreter", indexId.id()));
+ } else {
+ return interpreter.mapFlowRuleTableId(indexId.id()).get();
+ }
+ default:
+ throw new PiTranslationService.PiTranslationException(format(
+ "Unrecognized table ID type %s", tableId.type().name()));
+ }
+ }
+}
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
deleted file mode 100644
index 6874292..0000000
--- a/core/net/src/test/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright 2017-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.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.bmv2.model.Bmv2PipelineModelParser;
-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.DefaultPiPipeconf;
-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.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.*;
-import static org.onosproject.net.pi.impl.PiFlowRuleTranslator.MAX_PI_PRIORITY;
-
-/**
- * Tests for {@link PiFlowRuleTranslator}.
- */
-@SuppressWarnings("ConstantConditions")
-public class PiFlowRuleTranslatorTest {
-
- private static final String BMV2_JSON_PATH = "/org/onosproject/net/pi/impl/default.json";
- private static final short IN_PORT_MASK = 0x01ff; // 9-bit mask
- private static final short ETH_TYPE_MASK = (short) 0xffff;
-
- private Random random = new Random();
- private PiPipeconf pipeconf;
-
- @Before
- public void setUp() throws Exception {
- pipeconf = DefaultPiPipeconf.builder()
- .withId(new PiPipeconfId("mock-pipeconf"))
- .withPipelineModel(Bmv2PipelineModelParser.parse(this.getClass().getResource(BMV2_JSON_PATH)))
- .addBehaviour(PiPipelineInterpreter.class, MockInterpreter.class)
- .build();
- }
-
- @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.matchKey().fieldMatch(IN_PORT_ID).get();
- PiTernaryFieldMatch ethDstParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(ETH_DST_ID).get();
- PiTernaryFieldMatch ethSrcParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(ETH_SRC_ID).get();
- PiTernaryFieldMatch ethTypeParam = (PiTernaryFieldMatch) entry1.matchKey().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.matchKey().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 inPort match param mask",
- inPortParam.mask().asReadOnlyBuffer().getShort(), is(equalTo(IN_PORT_MASK)));
- assertThat("Incorrect ethDestMac match param value",
- ethDstParam.value().asArray(), is(equalTo(ethDstMac.toBytes())));
- assertThat("Incorrect ethDestMac match param mask",
- ethDstParam.mask().asArray(), is(equalTo(MacAddress.BROADCAST.toBytes())));
- assertThat("Incorrect ethSrcMac match param value",
- ethSrcParam.value().asArray(), is(equalTo(ethSrcMac.toBytes())));
- assertThat("Incorrect ethSrcMac match param mask",
- ethSrcParam.mask().asArray(), is(equalTo(MacAddress.BROADCAST.toBytes())));
- assertThat("Incorrect ethType match param value",
- ethTypeParam.value().asReadOnlyBuffer().getShort(), is(equalTo(ethType)));
- assertThat("Incorrect ethType match param mask",
- ethTypeParam.mask().asReadOnlyBuffer().getShort(), is(equalTo(ETH_TYPE_MASK)));
- assertThat("Incorrect priority value",
- entry1.priority().get(), is(equalTo(MAX_PI_PRIORITY - rule1.priority())));
- assertThat("Incorrect timeout value",
- entry1.timeout(), is(equalTo(expectedTimeout)));
-
- }
-}
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/PiTranslatorServiceTest.java b/core/net/src/test/java/org/onosproject/net/pi/impl/PiTranslatorServiceTest.java
new file mode 100644
index 0000000..2e6fa5f
--- /dev/null
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/PiTranslatorServiceTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2017-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.net.pi.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.TestApplicationId;
+import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.GroupId;
+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.flow.instructions.Instructions;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.pi.model.DefaultPiPipeconf;
+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.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroup;
+import org.onosproject.net.pi.runtime.PiActionGroupMember;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+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.PiActionProfileId;
+import org.onosproject.net.pi.runtime.PiGroupKey;
+import org.onosproject.net.pi.runtime.PiTableAction;
+import org.onosproject.net.pi.runtime.PiTableEntry;
+import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+
+import java.util.Collection;
+import java.util.List;
+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.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onlab.util.ImmutableByteSequence.fit;
+import static org.onosproject.net.group.GroupDescription.Type.SELECT;
+import static org.onosproject.net.pi.impl.MockInterpreter.*;
+import static org.onosproject.net.pi.impl.PiFlowRuleTranslator.MAX_PI_PRIORITY;
+
+/**
+ * Tests for {@link PiFlowRuleTranslator}.
+ */
+@SuppressWarnings("ConstantConditions")
+public class PiTranslatorServiceTest {
+
+ private static final String BMV2_JSON_PATH = "/org/onosproject/net/pi/impl/default.json";
+ private static final short IN_PORT_MASK = 0x01ff; // 9-bit mask
+ private static final short ETH_TYPE_MASK = (short) 0xffff;
+ private static final DeviceId DEVICE_ID = DeviceId.deviceId("device:dummy:1");
+ private static final ApplicationId APP_ID = TestApplicationId.create("dummy");
+ private static final PiTableId ECMP_TABLE_ID = PiTableId.of("ecmp");
+ private static final PiActionProfileId ACT_PROF_ID = PiActionProfileId.of("ecmp_selector");
+ private static final GroupId GROUP_ID = GroupId.valueOf(1);
+ private static final PiActionId EGRESS_PORT_ACTION_ID = PiActionId.of("set_egress_port");
+ private static final int PORT_BITWIDTH = 9;
+ private static final PiActionParamId PORT_PARAM_ID = PiActionParamId.of("port");
+ private static final List<GroupBucket> BUCKET_LIST = ImmutableList.of(outputBucket(1),
+ outputBucket(2),
+ outputBucket(3)
+ );
+ private static final PiGroupKey GROUP_KEY = new PiGroupKey(ECMP_TABLE_ID, ACT_PROF_ID, GROUP_ID.id());
+ private static final GroupBuckets BUCKETS = new GroupBuckets(BUCKET_LIST);
+ private static final GroupDescription GROUP_DESC =
+ new DefaultGroupDescription(DEVICE_ID, SELECT, BUCKETS, GROUP_KEY, GROUP_ID.id(), APP_ID);
+ private static final Group GROUP = new DefaultGroup(GROUP_ID, GROUP_DESC);
+ private static final int DEFAULT_MEMBER_WEIGHT = 1;
+ private static final int BASE_MEM_ID = 65535;
+ private Collection<PiActionGroupMember> expectedMembers;
+
+ private Random random = new Random();
+ private PiPipeconf pipeconf;
+
+ @Before
+ public void setUp() throws Exception {
+ pipeconf = DefaultPiPipeconf.builder()
+ .withId(new PiPipeconfId("mock-pipeconf"))
+ .withPipelineModel(Bmv2PipelineModelParser.parse(this.getClass().getResource(BMV2_JSON_PATH)))
+ .addBehaviour(PiPipelineInterpreter.class, MockInterpreter.class)
+ .build();
+
+ expectedMembers = ImmutableSet.of(outputMember(1),
+ outputMember(2),
+ outputMember(3));
+ }
+
+ @Test
+ public void testTranslateFlowRules() throws Exception {
+
+ 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(DEVICE_ID)
+ .forTable(tableId)
+ .fromApp(appId)
+ .withSelector(matchInPort1)
+ .withTreatment(outPort2)
+ .makeTemporary(timeout)
+ .withPriority(priority)
+ .build();
+
+ FlowRule rule2 = DefaultFlowRule.builder()
+ .forDevice(DEVICE_ID)
+ .forTable(tableId)
+ .fromApp(appId)
+ .withSelector(matchInPort1)
+ .withTreatment(outPort2)
+ .makeTemporary(timeout)
+ .withPriority(priority)
+ .build();
+
+ PiTableEntry entry1 = PiFlowRuleTranslator.translate(rule1, pipeconf, null);
+ PiTableEntry entry2 = PiFlowRuleTranslator.translate(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.matchKey().fieldMatch(IN_PORT_ID).get();
+ PiTernaryFieldMatch ethDstParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(ETH_DST_ID).get();
+ PiTernaryFieldMatch ethSrcParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(ETH_SRC_ID).get();
+ PiTernaryFieldMatch ethTypeParam = (PiTernaryFieldMatch) entry1.matchKey().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.matchKey().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 inPort match param mask",
+ inPortParam.mask().asReadOnlyBuffer().getShort(), is(equalTo(IN_PORT_MASK)));
+ assertThat("Incorrect ethDestMac match param value",
+ ethDstParam.value().asArray(), is(equalTo(ethDstMac.toBytes())));
+ assertThat("Incorrect ethDestMac match param mask",
+ ethDstParam.mask().asArray(), is(equalTo(MacAddress.BROADCAST.toBytes())));
+ assertThat("Incorrect ethSrcMac match param value",
+ ethSrcParam.value().asArray(), is(equalTo(ethSrcMac.toBytes())));
+ assertThat("Incorrect ethSrcMac match param mask",
+ ethSrcParam.mask().asArray(), is(equalTo(MacAddress.BROADCAST.toBytes())));
+ assertThat("Incorrect ethType match param value",
+ ethTypeParam.value().asReadOnlyBuffer().getShort(), is(equalTo(ethType)));
+ assertThat("Incorrect ethType match param mask",
+ ethTypeParam.mask().asReadOnlyBuffer().getShort(), is(equalTo(ETH_TYPE_MASK)));
+ assertThat("Incorrect priority value",
+ entry1.priority().get(), is(equalTo(MAX_PI_PRIORITY - rule1.priority())));
+ assertThat("Incorrect timeout value",
+ entry1.timeout(), is(equalTo(expectedTimeout)));
+
+ }
+
+ private static GroupBucket outputBucket(int portNum) {
+ ImmutableByteSequence paramVal = copyFrom(portNum);
+ PiActionParam param = new PiActionParam(PiActionParamId.of(PORT_PARAM_ID.name()), paramVal);
+ PiTableAction action = PiAction.builder().withId(EGRESS_PORT_ACTION_ID).withParameter(param).build();
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .add(Instructions.piTableAction(action))
+ .build();
+ return DefaultGroupBucket.createSelectGroupBucket(treatment);
+ }
+
+ private static PiActionGroupMember outputMember(int portNum)
+ throws ImmutableByteSequence.ByteSequenceTrimException {
+ PiActionParam param = new PiActionParam(PORT_PARAM_ID, fit(copyFrom(portNum), PORT_BITWIDTH));
+ PiAction piAction = PiAction.builder()
+ .withId(EGRESS_PORT_ACTION_ID)
+ .withParameter(param).build();
+ return PiActionGroupMember.builder()
+ .withAction(piAction)
+ .withId(PiActionGroupMemberId.of(BASE_MEM_ID + portNum))
+ .withWeight(DEFAULT_MEMBER_WEIGHT)
+ .build();
+ }
+
+ /**
+ * Test add group with buckets.
+ */
+ @Test
+ public void testTranslateGroups() throws Exception {
+
+ PiActionGroup piGroup1 = PiGroupTranslator.translate(GROUP, pipeconf, null);
+ PiActionGroup piGroup2 = PiGroupTranslator.translate(GROUP, pipeconf, null);
+
+ new EqualsTester()
+ .addEqualityGroup(piGroup1, piGroup2)
+ .testEquals();
+
+ assertThat("Group ID must be equal",
+ piGroup1.id().id(), is(equalTo(GROUP_ID.id())));
+ assertThat("Group type must be SELECT",
+ piGroup1.type(), is(equalTo(PiActionGroup.Type.SELECT)));
+ assertThat("Action profile ID must be equal",
+ piGroup1.actionProfileId(), is(equalTo(ACT_PROF_ID)));
+
+ // members installed
+ Collection<PiActionGroupMember> members = piGroup1.members();
+ assertThat("The number of group members must be equal",
+ piGroup1.members().size(), is(expectedMembers.size()));
+ assertThat("Group members must be equal",
+ members.containsAll(expectedMembers) && expectedMembers.containsAll(members));
+ }
+}
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index 54dd14c..2de7dcc 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -213,6 +213,7 @@
import org.onosproject.net.pi.runtime.PiExactFieldMatch;
import org.onosproject.net.pi.runtime.PiFieldMatch;
import org.onosproject.net.pi.runtime.PiActionProfileId;
+import org.onosproject.net.pi.runtime.PiGroupKey;
import org.onosproject.net.pi.runtime.PiHeaderFieldId;
import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
import org.onosproject.net.pi.runtime.PiMatchKey;
@@ -607,6 +608,7 @@
PiActionParamId.class,
PiExactFieldMatch.class,
PiFieldMatch.class,
+ PiGroupKey.class,
PiHeaderFieldId.class,
PiLpmFieldMatch.class,
PiMatchKey.class,