ONOS-7739 Support for P4Runtime multicast programming
Design doc: https://docs.google.com/document/d/13rkQlwr49M-uxQQEuxCMP7BFEPY2gtQ850Hn3gUfesU/edit#heading=h.lzdayib259sq
Change-Id: Ief00bec89fe5a9784b0ee13fdaafa3ae58ab654f
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiMulticastGroupTranslatorImpl.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiMulticastGroupTranslatorImpl.java
new file mode 100644
index 0000000..3d524c7
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiMulticastGroupTranslatorImpl.java
@@ -0,0 +1,107 @@
+/*
+ * 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.net.pi.impl;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
+import org.onosproject.net.pi.runtime.PiPreReplica;
+import org.onosproject.net.pi.service.PiTranslationException;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+
+/**
+ * Implementation of multicast group translation logic.
+ */
+final class PiMulticastGroupTranslatorImpl {
+
+ private PiMulticastGroupTranslatorImpl() {
+ // Hides constructor.
+ }
+
+ /**
+ * Returns a PI PRE multicast group entry equivalent to the given group, for
+ * the given pipeconf and device.
+ * <p>
+ * The passed group is expected to have type {@link GroupDescription.Type#ALL}.
+ *
+ * @param group group
+ * @return PI PRE entry
+ * @throws PiTranslationException if the group cannot be translated
+ */
+ static PiMulticastGroupEntry translate(Group group)
+ throws PiTranslationException {
+
+ checkNotNull(group);
+
+ if (!group.type().equals(GroupDescription.Type.ALL)) {
+ throw new PiTranslationException(format(
+ "group type %s not supported", group.type()));
+ }
+
+ final List<Instruction> instructions = group.buckets().buckets().stream()
+ .flatMap(b -> b.treatment().allInstructions().stream())
+ .collect(Collectors.toList());
+
+ final boolean hasNonOutputInstr = instructions.stream()
+ .anyMatch(i -> !i.type().equals(Instruction.Type.OUTPUT));
+
+ if (instructions.size() != group.buckets().buckets().size()
+ || hasNonOutputInstr) {
+ throw new PiTranslationException(
+ "support only groups with just one OUTPUT instruction per bucket");
+ }
+
+ final List<OutputInstruction> outInstructions = instructions.stream()
+ .map(i -> (OutputInstruction) i)
+ .collect(Collectors.toList());
+
+ return PiMulticastGroupEntry.builder()
+ .withGroupId(group.id().id())
+ .addReplicas(getReplicas(outInstructions))
+ .build();
+ }
+
+ private static Set<PiPreReplica> getReplicas(Collection<OutputInstruction> instructions) {
+ // Account for multiple replicas for the same port.
+ final Map<PortNumber, Set<PiPreReplica>> replicaMap = Maps.newHashMap();
+ final List<PortNumber> ports = instructions.stream()
+ .map(OutputInstruction::port)
+ .collect(Collectors.toList());
+ for (PortNumber port : ports) {
+ replicaMap.putIfAbsent(port, Sets.newHashSet());
+ // Use incremental instance IDs for replicas of the same port.
+ replicaMap.get(port).add(
+ new PiPreReplica(port, replicaMap.get(port).size() + 1));
+ }
+ return replicaMap.values().stream()
+ .flatMap(Collection::stream)
+ .collect(Collectors.toSet());
+ }
+}
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
index c69ced9..1ee53cf 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
@@ -31,6 +31,7 @@
import org.onosproject.net.pi.model.PiPipeconf;
import org.onosproject.net.pi.runtime.PiActionGroup;
import org.onosproject.net.pi.runtime.PiMeterCellConfig;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
import org.onosproject.net.pi.runtime.PiTableEntry;
import org.onosproject.net.pi.service.PiFlowRuleTranslationStore;
import org.onosproject.net.pi.service.PiFlowRuleTranslator;
@@ -38,6 +39,8 @@
import org.onosproject.net.pi.service.PiGroupTranslator;
import org.onosproject.net.pi.service.PiMeterTranslationStore;
import org.onosproject.net.pi.service.PiMeterTranslator;
+import org.onosproject.net.pi.service.PiMulticastGroupTranslationStore;
+import org.onosproject.net.pi.service.PiMulticastGroupTranslator;
import org.onosproject.net.pi.service.PiTranslationException;
import org.onosproject.net.pi.service.PiTranslationService;
import org.slf4j.Logger;
@@ -64,16 +67,21 @@
private PiGroupTranslationStore groupTranslationStore;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private PiMulticastGroupTranslationStore mcGroupTranslationStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private PiMeterTranslationStore meterTranslationStore;
private PiFlowRuleTranslator flowRuleTranslator;
private PiGroupTranslator groupTranslator;
+ private PiMulticastGroupTranslator mcGroupTranslator;
private PiMeterTranslator meterTranslator;
@Activate
public void activate() {
flowRuleTranslator = new InternalFlowRuleTranslator(flowRuleTranslationStore);
groupTranslator = new InternalGroupTranslator(groupTranslationStore);
+ mcGroupTranslator = new InternalMulticastGroupTranslator(mcGroupTranslationStore);
meterTranslator = new InternalMeterTranslator(meterTranslationStore);
log.info("Started");
}
@@ -101,6 +109,11 @@
return meterTranslator;
}
+ @Override
+ public PiMulticastGroupTranslator multicastGroupTranslator() {
+ return mcGroupTranslator;
+ }
+
private Device getDevice(DeviceId deviceId) throws PiTranslationException {
final Device device = deviceService.getDevice(deviceId);
if (device == null) {
@@ -141,6 +154,21 @@
}
}
+ private final class InternalMulticastGroupTranslator
+ extends AbstractPiTranslatorImpl<Group, PiMulticastGroupEntry>
+ implements PiMulticastGroupTranslator {
+
+ private InternalMulticastGroupTranslator(PiMulticastGroupTranslationStore store) {
+ super(store);
+ }
+
+ @Override
+ public PiMulticastGroupEntry translate(Group original, PiPipeconf pipeconf)
+ throws PiTranslationException {
+ return PiMulticastGroupTranslatorImpl.translate(original);
+ }
+ }
+
private final class InternalMeterTranslator
extends AbstractPiTranslatorImpl<Meter, PiMeterCellConfig>
implements PiMeterTranslator {
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorImplTest.java b/core/net/src/test/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorImplTest.java
new file mode 100644
index 0000000..15774ac
--- /dev/null
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorImplTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.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.PiMatchKey;
+import org.onosproject.net.pi.runtime.PiTableEntry;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+import org.onosproject.pipelines.basic.PipeconfLoader;
+
+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.pipelines.basic.BasicConstants.HDR_ETH_DST_ID;
+import static org.onosproject.pipelines.basic.BasicConstants.HDR_ETH_SRC_ID;
+import static org.onosproject.pipelines.basic.BasicConstants.HDR_ETH_TYPE_ID;
+import static org.onosproject.pipelines.basic.BasicConstants.HDR_IN_PORT_ID;
+import static org.onosproject.pipelines.basic.BasicConstants.TBL_TABLE0_ID;
+
+/**
+ * Test for {@link PiFlowRuleTranslatorImpl}.
+ */
+@SuppressWarnings("ConstantConditions")
+public class PiFlowRuleTranslatorImplTest {
+ 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 Random random = new Random();
+ private PiPipeconf pipeconf;
+
+ @Before
+ public void setUp() {
+ pipeconf = PipeconfLoader.BASIC_PIPECONF;
+ }
+
+ @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();
+
+ TrafficSelector emptySelector = DefaultTrafficSelector
+ .builder().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();
+
+ FlowRule defActionRule = DefaultFlowRule.builder()
+ .forDevice(DEVICE_ID)
+ .forTable(tableId)
+ .fromApp(appId)
+ .withSelector(emptySelector)
+ .withTreatment(outPort2)
+ .makeTemporary(timeout)
+ .withPriority(priority)
+ .build();
+
+ PiTableEntry entry1 = PiFlowRuleTranslatorImpl.translate(rule1, pipeconf, null);
+ PiTableEntry entry2 = PiFlowRuleTranslatorImpl.translate(rule2, pipeconf, null);
+ PiTableEntry defActionEntry = PiFlowRuleTranslatorImpl.translate(defActionRule, pipeconf, null);
+
+ // check equality, i.e. same rules must produce same entries
+ new EqualsTester()
+ .addEqualityGroup(rule1, rule2)
+ .addEqualityGroup(entry1, entry2)
+ .testEquals();
+
+ // parse values stored in entry1
+ PiTernaryFieldMatch inPortParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_IN_PORT_ID).get();
+ PiTernaryFieldMatch ethDstParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_ETH_DST_ID).get();
+ PiTernaryFieldMatch ethSrcParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_ETH_SRC_ID).get();
+ PiTernaryFieldMatch ethTypeParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_ETH_TYPE_ID).get();
+ Optional<Double> expectedTimeout = pipeconf.pipelineModel().table(TBL_TABLE0_ID).get().supportsAging()
+ ? Optional.of((double) rule1.timeout()) : Optional.empty();
+
+ // 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)));
+ // FIXME: re-enable when P4Runtime priority handling will be moved out of transltion service
+ // see PiFlowRuleTranslatorImpl
+ // assertThat("Incorrect priority value",
+ // entry1.priority().get(), is(equalTo(MAX_PI_PRIORITY - rule1.priority())));
+ assertThat("Incorrect timeout value",
+ entry1.timeout(), is(equalTo(expectedTimeout)));
+ assertThat("Match key should be empty",
+ defActionEntry.matchKey(), is(equalTo(PiMatchKey.EMPTY)));
+ assertThat("Priority should not be set", !defActionEntry.priority().isPresent());
+ }
+}
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/PiGroupTranslatorImplTest.java b/core/net/src/test/java/org/onosproject/net/pi/impl/PiGroupTranslatorImplTest.java
new file mode 100644
index 0000000..aeaa553
--- /dev/null
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/PiGroupTranslatorImplTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.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.util.ImmutableByteSequence;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+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.PiPipeconf;
+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.PiActionParam;
+import org.onosproject.net.pi.runtime.PiGroupKey;
+import org.onosproject.net.pi.runtime.PiTableAction;
+import org.onosproject.pipelines.basic.PipeconfLoader;
+
+import java.util.Collection;
+import java.util.List;
+
+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.onosproject.net.group.GroupDescription.Type.SELECT;
+import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRF_WCMP_SELECTOR_ID;
+import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRM_PORT_ID;
+import static org.onosproject.pipelines.basic.BasicConstants.ACT_SET_EGRESS_PORT_WCMP_ID;
+import static org.onosproject.pipelines.basic.BasicConstants.PORT_BITWIDTH;
+import static org.onosproject.pipelines.basic.BasicConstants.TBL_WCMP_TABLE_ID;
+
+/**
+ * Test for {@link PiGroupTranslatorImpl}.
+ */
+public class PiGroupTranslatorImplTest {
+
+ private static final DeviceId DEVICE_ID = DeviceId.deviceId("device:dummy:1");
+ private static final ApplicationId APP_ID = TestApplicationId.create("dummy");
+ private static final GroupId GROUP_ID = GroupId.valueOf(1);
+ private static final PiGroupKey GROUP_KEY = new PiGroupKey(
+ TBL_WCMP_TABLE_ID, ACT_PRF_WCMP_SELECTOR_ID, GROUP_ID.id());
+ private static final List<GroupBucket> BUCKET_LIST = ImmutableList.of(
+ selectOutputBucket(1),
+ selectOutputBucket(2),
+ selectOutputBucket(3));
+ private static final GroupBuckets BUCKETS = new GroupBuckets(BUCKET_LIST);
+ private static final GroupDescription SELECT_GROUP_DESC = new DefaultGroupDescription(
+ DEVICE_ID, SELECT, BUCKETS, GROUP_KEY, GROUP_ID.id(), APP_ID);
+ private static final Group SELECT_GROUP = new DefaultGroup(GROUP_ID, SELECT_GROUP_DESC);
+ private static final int DEFAULT_MEMBER_WEIGHT = 1;
+ private static final int BASE_MEM_ID = 65535;
+ private Collection<PiActionGroupMember> expectedMembers;
+
+ private PiPipeconf pipeconf;
+
+ @Before
+ public void setUp() throws Exception {
+ pipeconf = PipeconfLoader.BASIC_PIPECONF;
+ expectedMembers = ImmutableSet.of(outputMember(1),
+ outputMember(2),
+ outputMember(3));
+ }
+
+ private static GroupBucket selectOutputBucket(int portNum) {
+ ImmutableByteSequence paramVal = copyFrom(portNum);
+ PiActionParam param = new PiActionParam(ACT_PRM_PORT_ID, paramVal);
+ PiTableAction action = PiAction.builder().withId(ACT_SET_EGRESS_PORT_WCMP_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(ACT_PRM_PORT_ID, copyFrom(portNum).fit(PORT_BITWIDTH));
+ PiAction piAction = PiAction.builder()
+ .withId(ACT_SET_EGRESS_PORT_WCMP_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 = PiGroupTranslatorImpl.translate(SELECT_GROUP, pipeconf, null);
+ PiActionGroup piGroup2 = PiGroupTranslatorImpl.translate(SELECT_GROUP, pipeconf, null);
+
+ new EqualsTester()
+ .addEqualityGroup(piGroup1, piGroup2)
+ .testEquals();
+
+ assertThat("Group ID must be equal",
+ piGroup1.id().id(), is(equalTo(GROUP_ID.id())));
+ assertThat("Action profile ID must be equal",
+ piGroup1.actionProfileId(), is(equalTo(ACT_PRF_WCMP_SELECTOR_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/net/src/test/java/org/onosproject/net/pi/impl/PiMulticastGroupTranslatorImplTest.java b/core/net/src/test/java/org/onosproject/net/pi/impl/PiMulticastGroupTranslatorImplTest.java
new file mode 100644
index 0000000..475b7d8
--- /dev/null
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/PiMulticastGroupTranslatorImplTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.net.pi.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+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.runtime.PiGroupKey;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
+import org.onosproject.net.pi.runtime.PiPreReplica;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.onosproject.net.group.GroupDescription.Type.ALL;
+import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRF_WCMP_SELECTOR_ID;
+import static org.onosproject.pipelines.basic.BasicConstants.TBL_WCMP_TABLE_ID;
+
+/**
+ * Test for {@link PiMulticastGroupTranslatorImpl}.
+ */
+public class PiMulticastGroupTranslatorImplTest {
+ private static final DeviceId DEVICE_ID = DeviceId.deviceId("device:dummy:1");
+ private static final ApplicationId APP_ID = TestApplicationId.create("dummy");
+ private static final GroupId GROUP_ID = GroupId.valueOf(1);
+ private static final PiGroupKey GROUP_KEY = new PiGroupKey(
+ TBL_WCMP_TABLE_ID, ACT_PRF_WCMP_SELECTOR_ID, GROUP_ID.id());
+
+ private static final List<GroupBucket> BUCKET_LIST = ImmutableList.of(
+ allOutputBucket(1),
+ allOutputBucket(2),
+ allOutputBucket(3));
+ private static final List<GroupBucket> BUCKET_LIST_2 = ImmutableList.of(
+ allOutputBucket(1),
+ allOutputBucket(2),
+ allOutputBucket(2),
+ allOutputBucket(3),
+ allOutputBucket(3));
+
+ private static final Set<PiPreReplica> REPLICAS = ImmutableSet.of(
+ new PiPreReplica(PortNumber.portNumber(1), 1),
+ new PiPreReplica(PortNumber.portNumber(2), 1),
+ new PiPreReplica(PortNumber.portNumber(3), 1));
+ private static final Set<PiPreReplica> REPLICAS_2 = ImmutableSet.of(
+ new PiPreReplica(PortNumber.portNumber(1), 1),
+ new PiPreReplica(PortNumber.portNumber(2), 1),
+ new PiPreReplica(PortNumber.portNumber(2), 2),
+ new PiPreReplica(PortNumber.portNumber(3), 1),
+ new PiPreReplica(PortNumber.portNumber(3), 2));
+
+ private static final PiMulticastGroupEntry PI_MULTICAST_GROUP =
+ PiMulticastGroupEntry.builder()
+ .withGroupId(GROUP_ID.id())
+ .addReplicas(REPLICAS)
+ .build();
+ private static final PiMulticastGroupEntry PI_MULTICAST_GROUP_2 =
+ PiMulticastGroupEntry.builder()
+ .withGroupId(GROUP_ID.id())
+ .addReplicas(REPLICAS_2)
+ .build();
+
+ private static final GroupBuckets BUCKETS = new GroupBuckets(BUCKET_LIST);
+ private static final GroupBuckets BUCKETS_2 = new GroupBuckets(BUCKET_LIST_2);
+
+ private static final GroupDescription ALL_GROUP_DESC = new DefaultGroupDescription(
+ DEVICE_ID, ALL, BUCKETS, GROUP_KEY, GROUP_ID.id(), APP_ID);
+ private static final Group ALL_GROUP = new DefaultGroup(GROUP_ID, ALL_GROUP_DESC);
+
+ private static final GroupDescription ALL_GROUP_DESC_2 = new DefaultGroupDescription(
+ DEVICE_ID, ALL, BUCKETS_2, GROUP_KEY, GROUP_ID.id(), APP_ID);
+ private static final Group ALL_GROUP_2 = new DefaultGroup(GROUP_ID, ALL_GROUP_DESC_2);
+
+
+ private static GroupBucket allOutputBucket(int portNum) {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(portNum))
+ .build();
+ return DefaultGroupBucket.createAllGroupBucket(treatment);
+ }
+
+ @Test
+ public void testTranslatePreGroups() throws Exception {
+
+ PiMulticastGroupEntry multicastGroup = PiMulticastGroupTranslatorImpl
+ .translate(ALL_GROUP);
+ PiMulticastGroupEntry multicastGroup2 = PiMulticastGroupTranslatorImpl
+ .translate(ALL_GROUP_2);
+
+ new EqualsTester()
+ .addEqualityGroup(multicastGroup, PI_MULTICAST_GROUP)
+ .addEqualityGroup(multicastGroup2, PI_MULTICAST_GROUP_2)
+ .testEquals();
+ }
+}
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
deleted file mode 100644
index 9faeaba..0000000
--- a/core/net/src/test/java/org/onosproject/net/pi/impl/PiTranslatorServiceTest.java
+++ /dev/null
@@ -1,270 +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.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.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.PiPipeconf;
-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.PiActionParam;
-import org.onosproject.net.pi.runtime.PiGroupKey;
-import org.onosproject.net.pi.runtime.PiMatchKey;
-import org.onosproject.net.pi.runtime.PiTableAction;
-import org.onosproject.net.pi.runtime.PiTableEntry;
-import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
-import org.onosproject.pipelines.basic.PipeconfLoader;
-
-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.onosproject.net.group.GroupDescription.Type.SELECT;
-import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRF_WCMP_SELECTOR_ID;
-import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRM_PORT_ID;
-import static org.onosproject.pipelines.basic.BasicConstants.ACT_SET_EGRESS_PORT_WCMP_ID;
-import static org.onosproject.pipelines.basic.BasicConstants.HDR_ETH_DST_ID;
-import static org.onosproject.pipelines.basic.BasicConstants.HDR_ETH_SRC_ID;
-import static org.onosproject.pipelines.basic.BasicConstants.HDR_ETH_TYPE_ID;
-import static org.onosproject.pipelines.basic.BasicConstants.HDR_IN_PORT_ID;
-import static org.onosproject.pipelines.basic.BasicConstants.PORT_BITWIDTH;
-import static org.onosproject.pipelines.basic.BasicConstants.TBL_TABLE0_ID;
-import static org.onosproject.pipelines.basic.BasicConstants.TBL_WCMP_TABLE_ID;
-
-/**
- * Tests for {@link PiFlowRuleTranslatorImpl}.
- */
-@SuppressWarnings("ConstantConditions")
-public class PiTranslatorServiceTest {
-
- 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 GroupId GROUP_ID = GroupId.valueOf(1);
- private static final List<GroupBucket> BUCKET_LIST = ImmutableList.of(outputBucket(1),
- outputBucket(2),
- outputBucket(3)
- );
- private static final PiGroupKey GROUP_KEY = new PiGroupKey(TBL_WCMP_TABLE_ID, ACT_PRF_WCMP_SELECTOR_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 = PipeconfLoader.BASIC_PIPECONF;
- 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();
-
- TrafficSelector emptySelector = DefaultTrafficSelector
- .builder().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();
-
- FlowRule defActionRule = DefaultFlowRule.builder()
- .forDevice(DEVICE_ID)
- .forTable(tableId)
- .fromApp(appId)
- .withSelector(emptySelector)
- .withTreatment(outPort2)
- .makeTemporary(timeout)
- .withPriority(priority)
- .build();
-
- PiTableEntry entry1 = PiFlowRuleTranslatorImpl.translate(rule1, pipeconf, null);
- PiTableEntry entry2 = PiFlowRuleTranslatorImpl.translate(rule2, pipeconf, null);
- PiTableEntry defActionEntry = PiFlowRuleTranslatorImpl.translate(defActionRule, pipeconf, null);
-
- // check equality, i.e. same rules must produce same entries
- new EqualsTester()
- .addEqualityGroup(rule1, rule2)
- .addEqualityGroup(entry1, entry2)
- .testEquals();
-
- // parse values stored in entry1
- PiTernaryFieldMatch inPortParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_IN_PORT_ID).get();
- PiTernaryFieldMatch ethDstParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_ETH_DST_ID).get();
- PiTernaryFieldMatch ethSrcParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_ETH_SRC_ID).get();
- PiTernaryFieldMatch ethTypeParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_ETH_TYPE_ID).get();
- Optional<Double> expectedTimeout = pipeconf.pipelineModel().table(TBL_TABLE0_ID).get().supportsAging()
- ? Optional.of((double) rule1.timeout()) : Optional.empty();
-
- // 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)));
- // FIXME: re-enable when P4Runtime priority handling will be moved out of transltion service
- // see PiFlowRuleTranslatorImpl
- // assertThat("Incorrect priority value",
- // entry1.priority().get(), is(equalTo(MAX_PI_PRIORITY - rule1.priority())));
- assertThat("Incorrect timeout value",
- entry1.timeout(), is(equalTo(expectedTimeout)));
- assertThat("Match key should be empty",
- defActionEntry.matchKey(), is(equalTo(PiMatchKey.EMPTY)));
- assertThat("Priority should not be set", !defActionEntry.priority().isPresent());
-
- }
-
- private static GroupBucket outputBucket(int portNum) {
- ImmutableByteSequence paramVal = copyFrom(portNum);
- PiActionParam param = new PiActionParam(ACT_PRM_PORT_ID, paramVal);
- PiTableAction action = PiAction.builder().withId(ACT_SET_EGRESS_PORT_WCMP_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(ACT_PRM_PORT_ID, copyFrom(portNum).fit(PORT_BITWIDTH));
- PiAction piAction = PiAction.builder()
- .withId(ACT_SET_EGRESS_PORT_WCMP_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 = PiGroupTranslatorImpl.translate(GROUP, pipeconf, null);
- PiActionGroup piGroup2 = PiGroupTranslatorImpl.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("Action profile ID must be equal",
- piGroup1.actionProfileId(), is(equalTo(ACT_PRF_WCMP_SELECTOR_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));
- }
-}