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/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java
index a6c612b..0569f99 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java
@@ -46,5 +46,15 @@
/**
* Register entry.
*/
- REGISTER_CELL
+ REGISTER_CELL,
+
+ /**
+ * Packet Replication Engine (PRE) multicast group entry.
+ */
+ PRE_MULTICAST_GROUP_ENTRY,
+
+ /**
+ * Packet Replication Engine (PRE) clone session entry.
+ */
+ PRE_CLONE_SESSION_ENTRY
}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntry.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntry.java
new file mode 100644
index 0000000..5f68e40
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntry.java
@@ -0,0 +1,168 @@
+/*
+ * 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.runtime;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Collection;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Representation of multicast group entry of a protocol-independent packet
+ * replication engine (PRE).
+ */
+@Beta
+public final class PiMulticastGroupEntry implements PiPreEntry {
+
+ private final long groupId;
+ private final Set<PiPreReplica> replicas;
+
+ private PiMulticastGroupEntry(long groupId, Set<PiPreReplica> replicas) {
+ this.groupId = groupId;
+ this.replicas = replicas;
+ }
+
+ /**
+ * Returns the identifier of this multicast group, unique in the scope of a
+ * PRE instance.
+ *
+ * @return group entry ID
+ */
+ public long groupId() {
+ return groupId;
+ }
+
+ /**
+ * Returns the packet replicas provided by this multicast group.
+ *
+ * @return packet replicas
+ */
+ public Set<PiPreReplica> replicas() {
+ return replicas;
+ }
+
+ @Override
+ public PiPreEntryType preEntryType() {
+ return PiPreEntryType.MULTICAST_GROUP;
+ }
+
+ @Override
+ public PiEntityType piEntityType() {
+ return PiEntityType.PRE_MULTICAST_GROUP_ENTRY;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(groupId, replicas);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final PiMulticastGroupEntry other = (PiMulticastGroupEntry) obj;
+ return Objects.equal(this.groupId, other.groupId)
+ && Objects.equal(this.replicas, other.replicas);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("groupId", groupId)
+ .add("replicas", replicas)
+ .toString();
+ }
+
+ /**
+ * Returns a new builder of multicast group entries.
+ *
+ * @return builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder of PI multicast group entries.
+ */
+ public static final class Builder {
+
+ private Long groupId;
+ private ImmutableSet.Builder<PiPreReplica> replicaSetBuilder = ImmutableSet.builder();
+
+ private Builder() {
+ // Hide constructor.
+ }
+
+ /**
+ * Sets the identifier of this multicast group.
+ *
+ * @param groupId group ID
+ * @return this
+ */
+ public Builder withGroupId(long groupId) {
+ this.groupId = groupId;
+ return this;
+ }
+
+ /**
+ * Adds the given packet replica to this multicast group.
+ *
+ * @param replica packet replica
+ * @return this
+ */
+ public Builder addReplica(PiPreReplica replica) {
+ checkNotNull(replica);
+ replicaSetBuilder.add(replica);
+ return this;
+ }
+
+ /**
+ * Adds the given packet replicas to this multicast group.
+ *
+ * @param replicas packet replicas
+ * @return this
+ */
+ public Builder addReplicas(Collection<PiPreReplica> replicas) {
+ checkNotNull(replicas);
+ replicaSetBuilder.addAll(replicas);
+ return this;
+ }
+
+ /**
+ * Returns a new multicast group entry.
+ *
+ * @return multicast group entry
+ */
+ public PiMulticastGroupEntry build() {
+ checkNotNull(groupId, "Multicast group ID must be set");
+ final ImmutableSet<PiPreReplica> replicas = replicaSetBuilder.build();
+ checkArgument(!replicas.isEmpty(), "At least one replica must be defined");
+ return new PiMulticastGroupEntry(groupId, replicas);
+ }
+ }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntryHandle.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntryHandle.java
new file mode 100644
index 0000000..f9b1170
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntryHandle.java
@@ -0,0 +1,74 @@
+/*
+ * 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.runtime;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import org.onosproject.net.DeviceId;
+
+/**
+ * Global identifier of a PI multicast group entry applied to the packet
+ * replication engine of a device, uniquely defined by a device ID, and group
+ * ID.
+ */
+@Beta
+public final class PiMulticastGroupEntryHandle extends PiHandle<PiMulticastGroupEntry> {
+
+ private PiMulticastGroupEntryHandle(DeviceId deviceId, PiMulticastGroupEntry entry) {
+ super(deviceId, entry);
+ }
+
+ /**
+ * Creates a new handle for the given device ID and PI multicast group
+ * entry.
+ *
+ * @param deviceId device ID
+ * @param entry PI multicast group entry
+ * @return PI multicast group entry handle
+ */
+ public static PiMulticastGroupEntryHandle of(DeviceId deviceId,
+ PiMulticastGroupEntry entry) {
+ return new PiMulticastGroupEntryHandle(deviceId, entry);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(deviceId(), piEntity().groupId());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PiMulticastGroupEntryHandle that = (PiMulticastGroupEntryHandle) o;
+ return Objects.equal(deviceId(), that.deviceId()) &&
+ Objects.equal(piEntity().groupId(), that.piEntity().groupId());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("deviceId", deviceId())
+ .add("groupId", piEntity().groupId())
+ .toString();
+ }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPreEntry.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPreEntry.java
new file mode 100644
index 0000000..fb6c5e0
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPreEntry.java
@@ -0,0 +1,48 @@
+/*
+ * 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.runtime;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Configuration entry of a Packet Replication Engine (PRE) of
+ * protocol-independent pipeline.
+ */
+@Beta
+public interface PiPreEntry extends PiEntity {
+
+ /**
+ * Type of PRE entry.
+ */
+ enum PiPreEntryType {
+ /**
+ * Multicast group entry.
+ */
+ MULTICAST_GROUP,
+ /**
+ * Clone session entry.
+ */
+ CLONE_SESSION
+ }
+
+ /**
+ * Returns the type of this PRE entry.
+ *
+ * @return PRE entry type
+ */
+ PiPreEntryType preEntryType();
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPreReplica.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPreReplica.java
new file mode 100644
index 0000000..ea7003d
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPreReplica.java
@@ -0,0 +1,89 @@
+/*
+ * 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.runtime;
+
+import com.google.common.base.Objects;
+import org.onosproject.net.PortNumber;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+
+/**
+ * Representation of a packet replica used for multicast or cloning process in a
+ * protocol-independent packet replication engine.
+ * <p>
+ * Each replica is uniquely identified inside a given multicast group or clone
+ * session by the pair (egress port, instance ID).
+ */
+public class PiPreReplica {
+
+ private final PortNumber egressPort;
+ private final long instanceId;
+
+ /**
+ * Returns a new PRE packet replica for the given egress port and instance
+ * ID.
+ *
+ * @param egressPort egress port
+ * @param instanceId instance ID
+ */
+ public PiPreReplica(PortNumber egressPort, long instanceId) {
+ this.egressPort = checkNotNull(egressPort);
+ this.instanceId = instanceId;
+ }
+
+ /**
+ * Returns the egress port of this replica.
+ *
+ * @return egress port
+ */
+ public PortNumber egressPort() {
+ return egressPort;
+ }
+
+ /**
+ * Returns the instance ID of this replica.
+ *
+ * @return instance ID
+ */
+ public long instanceId() {
+ return instanceId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(egressPort, instanceId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final PiPreReplica other = (PiPreReplica) obj;
+ return Objects.equal(this.egressPort, other.egressPort)
+ && Objects.equal(this.instanceId, other.instanceId);
+ }
+
+ @Override
+ public String toString() {
+ return format("%s:%d", egressPort, instanceId);
+ }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiMulticastGroupTranslationStore.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiMulticastGroupTranslationStore.java
new file mode 100644
index 0000000..f961d84
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiMulticastGroupTranslationStore.java
@@ -0,0 +1,30 @@
+/*
+ * 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.service;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
+
+/**
+ * A PI translation store that keeps track of which groups have been
+ * translated to which PI PRE multicast groups.
+ */
+@Beta
+public interface PiMulticastGroupTranslationStore
+ extends PiTranslationStore<Group, PiMulticastGroupEntry> {
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiMulticastGroupTranslator.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiMulticastGroupTranslator.java
new file mode 100644
index 0000000..e687189
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiMulticastGroupTranslator.java
@@ -0,0 +1,29 @@
+/*
+ * 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.service;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
+
+/**
+ * A translator of groups to PI multicast group.
+ */
+@Beta
+public interface PiMulticastGroupTranslator
+ extends PiTranslator<Group, PiMulticastGroupEntry> {
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java
index 20cef35..20ea14d 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java
@@ -45,4 +45,12 @@
* @return meter translator
*/
PiMeterTranslator meterTranslator();
+
+ /**
+ * Returns a group translator for packet replication engine (PRE)
+ * multicast groups.
+ *
+ * @return multicast group translator
+ */
+ PiMulticastGroupTranslator multicastGroupTranslator();
}
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntryTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntryTest.java
new file mode 100644
index 0000000..7c060ef
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntryTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.runtime;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.PortNumber;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests for {@link PiMulticastGroupEntry}.
+ */
+public class PiMulticastGroupEntryTest {
+ private final long groupId1 = 1;
+ private final long groupId2 = 2;
+
+ private final long instanceId1 = 1;
+
+ private final PortNumber port1 = PortNumber.portNumber(1);
+ private final PortNumber port2 = PortNumber.portNumber(2);
+ private final PortNumber port3 = PortNumber.portNumber(3);
+
+ private final PiPreReplica replica1 = new PiPreReplica(port1, instanceId1);
+ private final PiPreReplica replica2 = new PiPreReplica(port2, instanceId1);
+ private final PiPreReplica replica3 = new PiPreReplica(port3, instanceId1);
+
+ private final PiMulticastGroupEntry group1 = PiMulticastGroupEntry.builder()
+ .withGroupId(groupId1)
+ .addReplica(replica1)
+ .addReplica(replica2)
+ .build();
+
+ private final PiMulticastGroupEntry sameAsGroup1 = PiMulticastGroupEntry.builder()
+ .withGroupId(groupId1)
+ .addReplica(replica1)
+ .addReplica(replica2)
+ .build();
+
+ private final PiMulticastGroupEntry group2 = PiMulticastGroupEntry.builder()
+ .withGroupId(groupId2)
+ .addReplica(replica1)
+ .addReplica(replica2)
+ .addReplica(replica3)
+ .build();
+
+ @Test
+ public void testPiMulticastGroupEntry() {
+ assertThat("Invalid group ID",
+ group1.groupId(), is(groupId1));
+ assertThat("Invalid replicas size",
+ group1.replicas().size(), is(2));
+ assertThat("Invalid replicas",
+ group1.replicas(), contains(replica1, replica2));
+
+ assertThat("Invalid group ID",
+ group2.groupId(), is(groupId2));
+ assertThat("Invalid replicas size",
+ group2.replicas().size(), is(3));
+ assertThat("Invalid replicas",
+ group2.replicas(), contains(replica1, replica2, replica3));
+ }
+
+ @Test
+ public void testEquality() {
+ new EqualsTester()
+ .addEqualityGroup(group1, sameAsGroup1)
+ .addEqualityGroup(group2)
+ .testEquals();
+ }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPreReplicaTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPreReplicaTest.java
new file mode 100644
index 0000000..85ea36e
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPreReplicaTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.runtime;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.PortNumber;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests for {@link PiPreReplica}.
+ */
+public class PiPreReplicaTest {
+
+ private final long instanceId1 = 1;
+ private final long instanceId2 = 2;
+ private final PortNumber port1 = PortNumber.portNumber(1);
+ private final PortNumber port2 = PortNumber.portNumber(2);
+
+ private final PiPreReplica replica1of1 = new PiPreReplica(port1, instanceId1);
+ private final PiPreReplica sameAsReplica1of1 = new PiPreReplica(port1, instanceId1);
+
+ private final PiPreReplica replica1of2 = new PiPreReplica(port2, instanceId1);
+ private final PiPreReplica sameAsReplica1of2 = new PiPreReplica(port2, instanceId1);
+
+ private final PiPreReplica replica2of2 = new PiPreReplica(port2, instanceId2);
+ private final PiPreReplica sameAsReplica2of2 = new PiPreReplica(port2, instanceId2);
+
+ @Test
+ public void testPiPreReplica() {
+ assertThat("Invalid port", replica1of1.egressPort(), is(port1));
+ assertThat("Invalid instance ID", replica1of1.instanceId(), is(instanceId1));
+ assertThat("Invalid port", replica1of2.egressPort(), is(port2));
+ assertThat("Invalid instance ID", replica1of2.instanceId(), is(instanceId1));
+ }
+
+ @Test
+ public void testEquality() {
+ new EqualsTester()
+ .addEqualityGroup(replica1of1, sameAsReplica1of1)
+ .addEqualityGroup(replica1of2, sameAsReplica1of2)
+ .addEqualityGroup(replica2of2, sameAsReplica2of2)
+ .testEquals();
+ }
+}
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));
- }
-}
diff --git a/core/store/dist/src/main/java/org/onosproject/store/pi/impl/DistributedPiMulticastGroupTranslationStore.java b/core/store/dist/src/main/java/org/onosproject/store/pi/impl/DistributedPiMulticastGroupTranslationStore.java
new file mode 100644
index 0000000..c95b185
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onosproject/store/pi/impl/DistributedPiMulticastGroupTranslationStore.java
@@ -0,0 +1,40 @@
+/*
+ * 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.store.pi.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
+import org.onosproject.net.pi.service.PiMulticastGroupTranslationStore;
+
+/**
+ * Distributed implementation of a PI translation store for multicast groups.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedPiMulticastGroupTranslationStore
+ extends AbstractDistributedPiTranslationStore<Group, PiMulticastGroupEntry>
+ implements PiMulticastGroupTranslationStore {
+
+ private static final String MAP_SIMPLE_NAME = "mc-group";
+
+ @Override
+ protected String mapSimpleName() {
+ return MAP_SIMPLE_NAME;
+ }
+}