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;
+ }
+}
diff --git a/drivers/bmv2/src/main/resources/bmv2-drivers.xml b/drivers/bmv2/src/main/resources/bmv2-drivers.xml
index 37b57e0..0575ee2 100644
--- a/drivers/bmv2/src/main/resources/bmv2-drivers.xml
+++ b/drivers/bmv2/src/main/resources/bmv2-drivers.xml
@@ -19,10 +19,12 @@
<behaviour api="org.onosproject.net.behaviour.PiPipelineProgrammable"
impl="org.onosproject.drivers.bmv2.Bmv2PipelineProgrammable"/>
<property name="tableDeleteBeforeUpdate">true</property>
+ <!--
<behaviour api="org.onosproject.net.group.GroupProgrammable"
impl="org.onosproject.drivers.bmv2.Bmv2GroupProgrammable"/>
<behaviour api="org.onosproject.net.device.DeviceHandshaker"
impl="org.onosproject.drivers.bmv2.Bmv2DeviceHandshaker"/>
+ -->
</driver>
</drivers>
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
index 8676aee..15c1a0d 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
@@ -16,13 +16,17 @@
package org.onosproject.drivers.p4runtime;
-import com.google.common.collect.Maps;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Striped;
+import org.apache.commons.lang3.tuple.Pair;
import org.onosproject.drivers.p4runtime.mirror.P4RuntimeGroupMirror;
+import org.onosproject.drivers.p4runtime.mirror.P4RuntimeMulticastGroupMirror;
import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
import org.onosproject.net.DeviceId;
import org.onosproject.net.group.DefaultGroup;
import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupDescription;
import org.onosproject.net.group.GroupOperation;
import org.onosproject.net.group.GroupOperations;
import org.onosproject.net.group.GroupProgrammable;
@@ -32,18 +36,20 @@
import org.onosproject.net.pi.runtime.PiActionGroup;
import org.onosproject.net.pi.runtime.PiActionGroupHandle;
import org.onosproject.net.pi.runtime.PiActionGroupMember;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntryHandle;
import org.onosproject.net.pi.service.PiGroupTranslator;
+import org.onosproject.net.pi.service.PiMulticastGroupTranslator;
import org.onosproject.net.pi.service.PiTranslatedEntity;
import org.onosproject.net.pi.service.PiTranslationException;
+import org.onosproject.p4runtime.api.P4RuntimeClient;
import org.slf4j.Logger;
import java.util.Collection;
import java.util.Collections;
-import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -55,6 +61,10 @@
/**
* Implementation of the group programmable behaviour for P4Runtime.
+ * <p>
+ * This implementation distinguishes between ALL groups, and other types. ALL
+ * groups are handled via PRE multicast group programming, while other types are
+ * handled via action profile group programming.
*/
public class P4RuntimeGroupProgrammable
extends AbstractP4RuntimeHandlerBehaviour
@@ -80,11 +90,12 @@
protected GroupStore groupStore;
private P4RuntimeGroupMirror groupMirror;
- private PiGroupTranslator translator;
+ private PiGroupTranslator groupTranslator;
+ private P4RuntimeMulticastGroupMirror mcGroupMirror;
+ private PiMulticastGroupTranslator mcGroupTranslator;
// Needed to synchronize operations over the same group.
- private static final Map<PiActionGroupHandle, Lock> GROUP_LOCKS =
- Maps.newConcurrentMap();
+ private static final Striped<Lock> STRIPED_LOCKS = Striped.lock(30);
@Override
protected boolean setupBehaviour() {
@@ -92,8 +103,10 @@
return false;
}
groupMirror = this.handler().get(P4RuntimeGroupMirror.class);
+ mcGroupMirror = this.handler().get(P4RuntimeMulticastGroupMirror.class);
groupStore = handler().get(GroupStore.class);
- translator = piTranslationService.groupTranslator();
+ groupTranslator = piTranslationService.groupTranslator();
+ mcGroupTranslator = piTranslationService.multicastGroupTranslator();
return true;
}
@@ -103,7 +116,17 @@
if (!setupBehaviour()) {
return;
}
- groupOps.operations().forEach(op -> processGroupOp(deviceId, op));
+ groupOps.operations().stream()
+ // Get group type and operation type
+ .map(op -> Pair.of(groupStore.getGroup(deviceId, op.groupId()),
+ op.opType()))
+ .forEach(pair -> {
+ if (pair.getLeft().type().equals(GroupDescription.Type.ALL)) {
+ processMcGroupOp(deviceId, pair.getLeft(), pair.getRight());
+ } else {
+ processGroupOp(deviceId, pair.getLeft(), pair.getRight());
+ }
+ });
}
@Override
@@ -111,42 +134,75 @@
if (!setupBehaviour()) {
return Collections.emptyList();
}
+ final ImmutableList.Builder<Group> groups = ImmutableList.builder();
+
if (!driverBoolProperty(IGNORE_DEVICE_WHEN_GET, DEFAULT_IGNORE_DEVICE_WHEN_GET)) {
- return pipeconf.pipelineModel().actionProfiles().stream()
- .map(PiActionProfileModel::id)
- .flatMap(this::streamGroupsFromDevice)
- .collect(Collectors.toList());
+ groups.addAll(pipeconf.pipelineModel().actionProfiles().stream()
+ .map(PiActionProfileModel::id)
+ .flatMap(this::streamGroupsFromDevice)
+ .iterator());
+ // FIXME: enable reading MC groups from device once reading from
+ // PRE is supported in PI
+ // groups.addAll(getMcGroupsFromDevice());
} else {
- return groupMirror.getAll(deviceId).stream()
- .map(TimedEntry::entry)
- .map(this::forgeGroupEntry)
- .collect(Collectors.toList());
+ groups.addAll(groupMirror.getAll(deviceId).stream()
+ .map(TimedEntry::entry)
+ .map(this::forgeGroupEntry)
+ .iterator());
}
+ // FIXME: same as before..
+ groups.addAll(mcGroupMirror.getAll(deviceId).stream()
+ .map(TimedEntry::entry)
+ .map(this::forgeMcGroupEntry)
+ .iterator());
+
+ return groups.build();
}
- private void processGroupOp(DeviceId deviceId, GroupOperation groupOp) {
- final Group pdGroup = groupStore.getGroup(deviceId, groupOp.groupId());
-
+ private void processGroupOp(DeviceId deviceId, Group pdGroup, GroupOperation.Type opType) {
final PiActionGroup piGroup;
try {
- piGroup = translator.translate(pdGroup, pipeconf);
+ piGroup = groupTranslator.translate(pdGroup, pipeconf);
} catch (PiTranslationException e) {
- log.warn("Unable translate group, aborting {} operation: {}",
- groupOp.opType(), e.getMessage());
+ log.warn("Unable to translate group, aborting {} operation: {} [{}]",
+ opType, e.getMessage(), pdGroup);
return;
}
-
final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup);
final PiActionGroup groupOnDevice = groupMirror.get(handle) == null
? null
: groupMirror.get(handle).entry();
- final Lock lock = GROUP_LOCKS.computeIfAbsent(handle, k -> new ReentrantLock());
+ final Lock lock = STRIPED_LOCKS.get(handle);
lock.lock();
try {
processPiGroup(handle, piGroup,
- groupOnDevice, pdGroup, groupOp.opType());
+ groupOnDevice, pdGroup, opType);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private void processMcGroupOp(DeviceId deviceId, Group pdGroup, GroupOperation.Type opType) {
+ final PiMulticastGroupEntry mcGroup;
+ try {
+ mcGroup = mcGroupTranslator.translate(pdGroup, pipeconf);
+ } catch (PiTranslationException e) {
+ log.warn("Unable to translate multicast group, aborting {} operation: {} [{}]",
+ opType, e.getMessage(), pdGroup);
+ return;
+ }
+ final PiMulticastGroupEntryHandle handle = PiMulticastGroupEntryHandle.of(
+ deviceId, mcGroup);
+ final PiMulticastGroupEntry groupOnDevice = mcGroupMirror.get(handle) == null
+ ? null
+ : mcGroupMirror.get(handle).entry();
+ final Lock lock = STRIPED_LOCKS.get(handle);
+ lock.lock();
+ try {
+ processMcGroup(handle, mcGroup,
+ groupOnDevice, pdGroup, opType);
} finally {
lock.unlock();
}
@@ -167,7 +223,7 @@
if (writeGroupToDevice(groupToApply)) {
groupMirror.put(handle, groupToApply);
- translator.learn(handle, new PiTranslatedEntity<>(
+ groupTranslator.learn(handle, new PiTranslatedEntity<>(
pdGroup, groupToApply, handle));
}
} else if (operationType == GroupOperation.Type.MODIFY) {
@@ -183,8 +239,8 @@
}
if (modifyGroupFromDevice(groupToApply, groupOnDevice)) {
groupMirror.put(handle, groupToApply);
- translator.learn(handle,
- new PiTranslatedEntity<>(pdGroup, groupToApply, handle));
+ groupTranslator.learn(handle,
+ new PiTranslatedEntity<>(pdGroup, groupToApply, handle));
}
} else {
if (groupOnDevice == null) {
@@ -194,11 +250,49 @@
}
if (deleteGroupFromDevice(groupOnDevice)) {
groupMirror.remove(handle);
- translator.forget(handle);
+ groupTranslator.forget(handle);
}
}
}
+ private void processMcGroup(PiMulticastGroupEntryHandle handle,
+ PiMulticastGroupEntry groupToApply,
+ PiMulticastGroupEntry groupOnDevice,
+ Group pdGroup, GroupOperation.Type opType) {
+ if (opType == GroupOperation.Type.DELETE) {
+ if (writeMcGroupOnDevice(groupToApply, DELETE)) {
+ mcGroupMirror.remove(handle);
+ mcGroupTranslator.forget(handle);
+ }
+ return;
+ }
+
+ final P4RuntimeClient.WriteOperationType p4OpType =
+ opType == GroupOperation.Type.ADD ? INSERT : MODIFY;
+
+ if (driverBoolProperty(CHECK_MIRROR_BEFORE_UPDATE,
+ DEFAULT_CHECK_MIRROR_BEFORE_UPDATE)
+ && p4OpType == MODIFY
+ && groupOnDevice != null
+ && groupOnDevice.equals(groupToApply)) {
+ // Ignore.
+ return;
+ }
+
+ if (writeMcGroupOnDevice(groupToApply, p4OpType)) {
+ mcGroupMirror.put(handle, groupToApply);
+ mcGroupTranslator.learn(handle, new PiTranslatedEntity<>(
+ pdGroup, groupToApply, handle));
+ }
+ }
+
+ private boolean writeMcGroupOnDevice(PiMulticastGroupEntry group, P4RuntimeClient.WriteOperationType opType) {
+ return getFutureWithDeadline(
+ client.writePreMulticastGroupEntries(
+ Collections.singleton(group), opType),
+ "performing multicast group " + opType, false);
+ }
+
private boolean modifyGroupFromDevice(PiActionGroup groupToApply, PiActionGroup groupOnDevice) {
PiActionProfileId groupProfileId = groupToApply.actionProfileId();
Collection<PiActionGroupMember> membersToRemove = Sets.newHashSet(groupOnDevice.members());
@@ -287,9 +381,19 @@
.filter(Objects::nonNull);
}
+ private Collection<Group> getMcGroupsFromDevice() {
+ Collection<PiMulticastGroupEntry> groups = getFutureWithDeadline(
+ client.readAllMulticastGroupEntries(),
+ "dumping multicast groups", Collections.emptyList());
+ return groups.stream()
+ .map(this::forgeMcGroupEntry)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
private Group forgeGroupEntry(PiActionGroup piGroup) {
final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup);
- if (!translator.lookup(handle).isPresent()) {
+ if (!groupTranslator.lookup(handle).isPresent()) {
log.warn("Missing PI group from translation store: {} - {}:{}",
pipeconf.id(), piGroup.actionProfileId(),
piGroup.id());
@@ -297,7 +401,25 @@
}
final long life = groupMirror.get(handle) != null
? groupMirror.get(handle).lifeSec() : 0;
- final Group original = translator.lookup(handle).get().original();
+ final Group original = groupTranslator.lookup(handle).get().original();
+ return addedGroup(original, life);
+ }
+
+ private Group forgeMcGroupEntry(PiMulticastGroupEntry mcGroup) {
+ final PiMulticastGroupEntryHandle handle = PiMulticastGroupEntryHandle.of(
+ deviceId, mcGroup);
+ if (!mcGroupTranslator.lookup(handle).isPresent()) {
+ log.warn("Missing PI multicast group {} from translation store",
+ mcGroup.groupId());
+ return null;
+ }
+ final long life = mcGroupMirror.get(handle) != null
+ ? mcGroupMirror.get(handle).lifeSec() : 0;
+ final Group original = mcGroupTranslator.lookup(handle).get().original();
+ return addedGroup(original, life);
+ }
+
+ private Group addedGroup(Group original, long life) {
final DefaultGroup forgedGroup = new DefaultGroup(original.id(), original);
forgedGroup.setState(Group.GroupState.ADDED);
forgedGroup.setLife(life);
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMulticastGroupMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMulticastGroupMirror.java
new file mode 100644
index 0000000..4cfe0c5
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMulticastGroupMirror.java
@@ -0,0 +1,50 @@
+/*
+ * 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.drivers.p4runtime.mirror;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntryHandle;
+import org.onosproject.store.serializers.KryoNamespaces;
+
+/**
+ * Distributed implementation of a P4Runtime multicast group mirror.
+ */
+@Component(immediate = true)
+@Service
+public final class DistributedP4RuntimeMulticastGroupMirror
+ extends AbstractDistributedP4RuntimeMirror
+ <PiMulticastGroupEntryHandle, PiMulticastGroupEntry>
+ implements P4RuntimeMulticastGroupMirror {
+
+ private static final String DIST_MAP_NAME = "onos-p4runtime-mc-group-mirror";
+
+ @Override
+ String mapName() {
+ return DIST_MAP_NAME;
+ }
+
+ @Override
+ KryoNamespace storeSerializer() {
+ return KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(TimedEntry.class)
+ .build();
+ }
+}
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMulticastGroupMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMulticastGroupMirror.java
new file mode 100644
index 0000000..d901f29
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMulticastGroupMirror.java
@@ -0,0 +1,27 @@
+/*
+ * 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.drivers.p4runtime.mirror;
+
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntryHandle;
+
+/**
+ * Mirror of multicast groups installed on a P4Runtime device.
+ */
+public interface P4RuntimeMulticastGroupMirror
+ extends P4RuntimeMirror<PiMulticastGroupEntryHandle, PiMulticastGroupEntry> {
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
index d8dcc8a..2ee66aa 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
@@ -17,7 +17,6 @@
package org.onosproject.pipelines.fabric.pipeliner;
import org.onlab.packet.VlanId;
-import org.onlab.util.ImmutableByteSequence;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.driver.Driver;
@@ -51,6 +50,7 @@
import org.slf4j.Logger;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
import static org.slf4j.LoggerFactory.getLogger;
@@ -160,12 +160,16 @@
}
}
- private PortNumber getOutputPort(TrafficTreatment treatment) {
+ private Optional<OutputInstruction> getOutputInstruction(TrafficTreatment treatment) {
return treatment.allInstructions()
.stream()
.filter(inst -> inst.type() == Instruction.Type.OUTPUT)
.map(inst -> (OutputInstruction) inst)
- .findFirst()
+ .findFirst();
+ }
+
+ private PortNumber getOutputPort(TrafficTreatment treatment) {
+ return getOutputInstruction(treatment)
.map(OutputInstruction::port)
.orElse(null);
}
@@ -302,60 +306,81 @@
}
private void processBroadcastNext(NextObjective next, PipelinerTranslationResult.Builder resultBuilder) {
- int groupId = next.id();
- List<GroupBucket> bucketList = next.next().stream()
- .filter(treatment -> treatment != null)
- .map(DefaultGroupBucket::createAllGroupBucket)
- .collect(Collectors.toList());
-
- if (bucketList.size() != next.next().size()) {
- // some action not converted
- // set error
- log.warn("Expected bucket size {}, got {}", next.next().size(), bucketList.size());
+ final GroupDescription allGroup = getAllGroup(next);
+ if (allGroup == null) {
+ // Error already logged.
resultBuilder.setError(ObjectiveError.BADPARAMS);
return;
}
- GroupBuckets buckets = new GroupBuckets(bucketList);
- //Used DefaultGroupKey instead of PiGroupKey
- //as we don't have any action profile to apply to the groups of ALL type
- GroupKey groupKey = new DefaultGroupKey(FabricPipeliner.KRYO.serialize(groupId));
-
- resultBuilder.addGroup(new DefaultGroupDescription(deviceId,
- GroupDescription.Type.ALL,
- buckets,
- groupKey,
- groupId,
- next.appId()));
+ resultBuilder.addGroup(allGroup);
//flow rule
- TrafficSelector selector = buildNextIdSelector(next.id());
- PiActionParam groupIdParam = new PiActionParam(FabricConstants.GID,
- ImmutableByteSequence.copyFrom(groupId));
+ final TrafficSelector selector = buildNextIdSelector(next.id());
+ final PiActionParam groupIdParam = new PiActionParam(
+ FabricConstants.GID, allGroup.givenGroupId());
- PiAction setMcGroupAction = PiAction.builder()
+ final PiAction setMcGroupAction = PiAction.builder()
.withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_MCAST_GROUP)
.withParameter(groupIdParam)
.build();
- TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.piTableAction(setMcGroupAction)
.build();
- resultBuilder.addFlowRule(DefaultFlowRule.builder()
- .withSelector(selector)
- .withTreatment(treatment)
- .forTable(FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST)
- .makePermanent()
- .withPriority(next.priority())
- .forDevice(deviceId)
- .fromApp(next.appId())
- .build());
+ resultBuilder.addFlowRule(
+ DefaultFlowRule.builder()
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .forTable(FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST)
+ .makePermanent()
+ .withPriority(next.priority())
+ .forDevice(deviceId)
+ .fromApp(next.appId())
+ .build());
// Egress VLAN handling
- next.next().forEach(trafficTreatment -> {
- PortNumber outputPort = getOutputPort(trafficTreatment);
- if (includesPopVlanInst(trafficTreatment) && outputPort != null) {
+ next.next().forEach(t -> {
+ PortNumber outputPort = getOutputPort(t);
+ if (includesPopVlanInst(t) && outputPort != null) {
processVlanPopRule(outputPort, next, resultBuilder);
}
+ if (t.allInstructions().size() > 2) {
+ // More than OUTPUT and VLAN_POP...
+ log.warn("Some instructions of BROADCAST NextObjective might" +
+ "not have been applied, supported only " +
+ "OUTPUT and VLAN_POP, but found {}", t);
+ }
});
}
+
+ private GroupDescription getAllGroup(NextObjective next) {
+ final List<GroupBucket> bucketList = next.next().stream()
+ .map(this::getOutputInstruction)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .map(i -> DefaultTrafficTreatment.builder().add(i).build())
+ .map(DefaultGroupBucket::createAllGroupBucket)
+ .collect(Collectors.toList());
+
+ if (bucketList.size() != next.next().size()) {
+ log.warn("Got BROADCAST NextObjective with {} treatments but " +
+ "only {} have OUTPUT instructions, cannot " +
+ "translate to ALL groups",
+ next.next().size(), bucketList.size());
+ return null;
+ }
+
+ final int groupId = next.id();
+ final GroupBuckets buckets = new GroupBuckets(bucketList);
+ // Used DefaultGroupKey instead of PiGroupKey
+ // as we don't have any action profile to apply to the groups of ALL type
+ final GroupKey groupKey = new DefaultGroupKey(FabricPipeliner.KRYO.serialize(groupId));
+
+ return new DefaultGroupDescription(deviceId,
+ GroupDescription.Type.ALL,
+ buckets,
+ groupKey,
+ groupId,
+ next.appId());
+ }
}
diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java
index bfae578..bd57937 100644
--- a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java
+++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java
@@ -350,7 +350,13 @@
//create the expected group
GroupDescription actualGroup = groupsInstalled.get(0);
- List<TrafficTreatment> treatments = ImmutableList.of(treatment1, treatment2);
+ TrafficTreatment groupTreatment1 = DefaultTrafficTreatment.builder()
+ .setOutput(PORT_1)
+ .build();
+ TrafficTreatment groupTreatment2 = DefaultTrafficTreatment.builder()
+ .setOutput(PORT_2)
+ .build();
+ List<TrafficTreatment> treatments = ImmutableList.of(groupTreatment1, groupTreatment2);
List<GroupBucket> buckets = treatments.stream()
.map(DefaultGroupBucket::createAllGroupBucket)
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
index a9a6e08..93adf11 100644
--- a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
@@ -28,6 +28,7 @@
import org.onosproject.net.pi.runtime.PiCounterCellId;
import org.onosproject.net.pi.runtime.PiMeterCellConfig;
import org.onosproject.net.pi.runtime.PiMeterCellId;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
import org.onosproject.net.pi.runtime.PiPacketOperation;
import org.onosproject.net.pi.runtime.PiTableEntry;
@@ -225,4 +226,23 @@
*/
CompletableFuture<Boolean> writeMeterCells(
Collection<PiMeterCellConfig> cellConfigs, PiPipeconf pipeconf);
+
+ /**
+ * Performs the given write operation for the given PI multicast groups
+ * entries.
+ *
+ * @param entries multicast group entries
+ * @param opType write operation type
+ * @return true if the operation was successful, false otherwise
+ */
+ CompletableFuture<Boolean> writePreMulticastGroupEntries(
+ Collection<PiMulticastGroupEntry> entries,
+ WriteOperationType opType);
+
+ /**
+ * Returns all multicast groups on device.
+ *
+ * @return multicast groups
+ */
+ CompletableFuture<Collection<PiMulticastGroupEntry>> readAllMulticastGroupEntries();
}
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MulticastGroupEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MulticastGroupEntryCodec.java
new file mode 100644
index 0000000..0be1ae8
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MulticastGroupEntryCodec.java
@@ -0,0 +1,70 @@
+/*
+ * 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.p4runtime.ctl;
+
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
+import org.onosproject.net.pi.runtime.PiPreReplica;
+import p4.v1.P4RuntimeOuterClass.MulticastGroupEntry;
+import p4.v1.P4RuntimeOuterClass.Replica;
+
+/**
+ * A coded of {@link PiMulticastGroupEntry} to P4Runtime MulticastGroupEntry
+ * messages, and vice versa.
+ */
+final class MulticastGroupEntryCodec {
+
+ private MulticastGroupEntryCodec() {
+ // Hides constructor.
+ }
+
+ /**
+ * Returns a P4Runtime MulticastGroupEntry message equivalent to the given
+ * PiMulticastGroupEntry.
+ *
+ * @param piEntry PiMulticastGroupEntry
+ * @return P4Runtime MulticastGroupEntry message
+ */
+ static MulticastGroupEntry encode(PiMulticastGroupEntry piEntry) {
+ final MulticastGroupEntry.Builder msgBuilder = MulticastGroupEntry.newBuilder();
+ msgBuilder.setMulticastGroupId(piEntry.groupId());
+ piEntry.replicas().stream()
+ .map(r -> Replica.newBuilder()
+ .setEgressPort(r.egressPort().toLong())
+ .setInstance(r.instanceId())
+ .build())
+ .forEach(msgBuilder::addReplicas);
+ return msgBuilder.build();
+ }
+
+ /**
+ * Returns a PiMulticastGroupEntry equivalent to the given P4Runtime
+ * MulticastGroupEntry message.
+ *
+ * @param msg P4Runtime MulticastGroupEntry message
+ * @return PiMulticastGroupEntry
+ */
+ static PiMulticastGroupEntry decode(MulticastGroupEntry msg) {
+ final PiMulticastGroupEntry.Builder piEntryBuilder = PiMulticastGroupEntry.builder();
+ piEntryBuilder.withGroupId(msg.getMulticastGroupId());
+ msg.getReplicasList().stream()
+ .map(r -> new PiPreReplica(
+ PortNumber.portNumber(r.getEgressPort()), r.getInstance()))
+ .forEach(piEntryBuilder::addReplica);
+ return piEntryBuilder.build();
+ }
+}
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
index a20a0e6..b4d88a7 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
@@ -44,6 +44,7 @@
import org.onosproject.net.pi.runtime.PiEntity;
import org.onosproject.net.pi.runtime.PiMeterCellConfig;
import org.onosproject.net.pi.runtime.PiMeterCellId;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
import org.onosproject.net.pi.runtime.PiPacketOperation;
import org.onosproject.net.pi.runtime.PiTableEntry;
import org.onosproject.net.pi.service.PiPipeconfService;
@@ -59,6 +60,8 @@
import p4.v1.P4RuntimeOuterClass.Entity;
import p4.v1.P4RuntimeOuterClass.ForwardingPipelineConfig;
import p4.v1.P4RuntimeOuterClass.MasterArbitrationUpdate;
+import p4.v1.P4RuntimeOuterClass.MulticastGroupEntry;
+import p4.v1.P4RuntimeOuterClass.PacketReplicationEngineEntry;
import p4.v1.P4RuntimeOuterClass.ReadRequest;
import p4.v1.P4RuntimeOuterClass.ReadResponse;
import p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest;
@@ -95,9 +98,11 @@
import static org.slf4j.LoggerFactory.getLogger;
import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.ACTION_PROFILE_GROUP;
import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.ACTION_PROFILE_MEMBER;
+import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.PACKET_REPLICATION_ENGINE_ENTRY;
import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.TABLE_ENTRY;
import static p4.v1.P4RuntimeOuterClass.PacketIn;
import static p4.v1.P4RuntimeOuterClass.PacketOut;
+import static p4.v1.P4RuntimeOuterClass.PacketReplicationEngineEntry.TypeCase.MULTICAST_GROUP_ENTRY;
import static p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest.Action.VERIFY_AND_COMMIT;
/**
@@ -155,8 +160,8 @@
}
/**
- * Submits a task for async execution via the given executor.
- * All tasks submitted with this method will be executed sequentially.
+ * Submits a task for async execution via the given executor. All tasks
+ * submitted with this method will be executed sequentially.
*/
private <U> CompletableFuture<U> supplyWithExecutor(
Supplier<U> supplier, String opDescription, Executor executor) {
@@ -292,6 +297,20 @@
}
@Override
+ public CompletableFuture<Boolean> writePreMulticastGroupEntries(
+ Collection<PiMulticastGroupEntry> entries,
+ WriteOperationType opType) {
+ return supplyInContext(() -> doWriteMulticastGroupEntries(entries, opType),
+ "writePreMulticastGroupEntries");
+ }
+
+ @Override
+ public CompletableFuture<Collection<PiMulticastGroupEntry>> readAllMulticastGroupEntries() {
+ return supplyInContext(this::doReadAllMulticastGroupEntries,
+ "readAllMulticastGroupEntries");
+ }
+
+ @Override
public CompletableFuture<Collection<PiMeterCellConfig>> readMeterCells(Set<PiMeterCellId> cellIds,
PiPipeconf pipeconf) {
return supplyInContext(() -> doReadMeterCells(cellIds, pipeconf),
@@ -300,7 +319,7 @@
@Override
public CompletableFuture<Collection<PiMeterCellConfig>> readAllMeterCells(Set<PiMeterId> meterIds,
- PiPipeconf pipeconf) {
+ PiPipeconf pipeconf) {
return supplyInContext(() -> doReadAllMeterCells(meterIds, pipeconf),
"readAllMeterCells-" + meterIds.hashCode());
}
@@ -319,7 +338,7 @@
private boolean sendMasterArbitrationUpdate(Uint128 electionId) {
log.info("Sending arbitration update to {}... electionId={}",
- deviceId, uint128ToBigInteger(electionId));
+ deviceId, uint128ToBigInteger(electionId));
try {
streamRequestObserver.onNext(
StreamMessageRequest.newBuilder()
@@ -395,8 +414,6 @@
private boolean doWriteTableEntries(Collection<PiTableEntry> piTableEntries, WriteOperationType opType,
PiPipeconf pipeconf) {
- WriteRequest.Builder writeRequestBuilder = WriteRequest.newBuilder();
-
if (piTableEntries.size() == 0) {
return true;
}
@@ -419,19 +436,7 @@
return false;
}
- writeRequestBuilder
- .setDeviceId(p4DeviceId)
- .setElectionId(clientElectionId)
- .addAllUpdates(updateMsgs)
- .build();
-
- try {
- blockingStub.write(writeRequestBuilder.build());
- return true;
- } catch (StatusRuntimeException e) {
- checkAndLogWriteErrors(piTableEntries, e, opType, "table entry");
- return false;
- }
+ return write(updateMsgs, piTableEntries, opType, "table entry");
}
private Collection<PiTableEntry> doDumpTable(PiTableId piTableId, PiPipeconf pipeconf) {
@@ -444,6 +449,10 @@
tableId = 0;
} else {
P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+ if (browser == null) {
+ log.warn("Unable to get a P4Info browser for pipeconf {}", pipeconf);
+ return Collections.emptyList();
+ }
try {
tableId = browser.tables().getByName(piTableId.id()).getPreamble().getId();
} catch (P4InfoBrowser.NotFoundException e) {
@@ -616,18 +625,7 @@
return true;
}
- WriteRequest writeRequestMsg = WriteRequest.newBuilder()
- .setDeviceId(p4DeviceId)
- .setElectionId(clientElectionId)
- .addAllUpdates(updateMsgs)
- .build();
- try {
- blockingStub.write(writeRequestMsg);
- return true;
- } catch (StatusRuntimeException e) {
- checkAndLogWriteErrors(members, e, opType, "group member");
- return false;
- }
+ return write(updateMsgs, members, opType, "group member");
}
private Collection<PiActionGroup> doDumpGroups(PiActionProfileId piActionProfileId, PiPipeconf pipeconf) {
@@ -762,23 +760,15 @@
return false;
}
- final WriteRequest writeRequestMsg = WriteRequest.newBuilder()
- .setDeviceId(p4DeviceId)
- .setElectionId(clientElectionId)
- .addUpdates(Update.newBuilder()
- .setEntity(Entity.newBuilder()
- .setActionProfileGroup(actionProfileGroup)
- .build())
- .setType(UPDATE_TYPES.get(opType))
- .build())
+ final Update updateMsg = Update.newBuilder()
+ .setEntity(Entity.newBuilder()
+ .setActionProfileGroup(actionProfileGroup)
+ .build())
+ .setType(UPDATE_TYPES.get(opType))
.build();
- try {
- blockingStub.write(writeRequestMsg);
- return true;
- } catch (StatusRuntimeException e) {
- checkAndLogWriteErrors(Collections.singleton(group), e, opType, "group");
- return false;
- }
+
+ return write(Collections.singleton(updateMsg), Collections.singleton(group),
+ opType, "group");
}
private Collection<PiMeterCellConfig> doReadAllMeterCells(
@@ -830,11 +820,9 @@
return MeterEntryCodec.decodeMeterEntities(responseEntities, pipeconf);
}
- private boolean doWriteMeterCells(Collection<PiMeterCellConfig> cellIds, PiPipeconf pipeconf) {
+ private boolean doWriteMeterCells(Collection<PiMeterCellConfig> cellConfigs, PiPipeconf pipeconf) {
- WriteRequest.Builder writeRequestBuilder = WriteRequest.newBuilder();
-
- Collection<Update> updateMsgs = MeterEntryCodec.encodePiMeterCellConfigs(cellIds, pipeconf)
+ Collection<Update> updateMsgs = MeterEntryCodec.encodePiMeterCellConfigs(cellConfigs, pipeconf)
.stream()
.map(meterEntryMsg ->
Update.newBuilder()
@@ -847,19 +835,91 @@
return true;
}
- writeRequestBuilder
+ return write(updateMsgs, cellConfigs, WriteOperationType.MODIFY, "meter cell config");
+ }
+
+ private boolean doWriteMulticastGroupEntries(
+ Collection<PiMulticastGroupEntry> entries,
+ WriteOperationType opType) {
+
+ final List<Update> updateMsgs = entries.stream()
+ .map(MulticastGroupEntryCodec::encode)
+ .map(mcMsg -> PacketReplicationEngineEntry.newBuilder()
+ .setMulticastGroupEntry(mcMsg)
+ .build())
+ .map(preMsg -> Entity.newBuilder()
+ .setPacketReplicationEngineEntry(preMsg)
+ .build())
+ .map(entityMsg -> Update.newBuilder()
+ .setEntity(entityMsg)
+ .setType(UPDATE_TYPES.get(opType))
+ .build())
+ .collect(Collectors.toList());
+ return write(updateMsgs, entries, opType, "multicast group entry");
+ }
+
+ private Collection<PiMulticastGroupEntry> doReadAllMulticastGroupEntries() {
+
+ final Entity entity = Entity.newBuilder()
+ .setPacketReplicationEngineEntry(
+ PacketReplicationEngineEntry.newBuilder()
+ .setMulticastGroupEntry(
+ MulticastGroupEntry.newBuilder()
+ .build())
+ .build())
+ .build();
+
+ final ReadRequest req = ReadRequest.newBuilder()
+ .setDeviceId(p4DeviceId)
+ .addEntities(entity)
+ .build();
+
+ Iterator<ReadResponse> responses;
+ try {
+ responses = blockingStub.read(req);
+ } catch (StatusRuntimeException e) {
+ log.warn("Unable to read multicast group entries from {}: {}", deviceId, e.getMessage());
+ return Collections.emptyList();
+ }
+
+ Iterable<ReadResponse> responseIterable = () -> responses;
+ final List<PiMulticastGroupEntry> mcEntries = StreamSupport
+ .stream(responseIterable.spliterator(), false)
+ .map(ReadResponse::getEntitiesList)
+ .flatMap(List::stream)
+ .filter(e -> e.getEntityCase()
+ .equals(PACKET_REPLICATION_ENGINE_ENTRY))
+ .map(Entity::getPacketReplicationEngineEntry)
+ .filter(e -> e.getTypeCase().equals(MULTICAST_GROUP_ENTRY))
+ .map(PacketReplicationEngineEntry::getMulticastGroupEntry)
+ .map(MulticastGroupEntryCodec::decode)
+ .collect(Collectors.toList());
+
+ log.debug("Retrieved {} multicast group entries from {}...",
+ mcEntries.size(), deviceId);
+
+ return mcEntries;
+ }
+
+ private <E extends PiEntity> boolean write(Collection<Update> updates,
+ Collection<E> writeEntities,
+ WriteOperationType opType,
+ String entryType) {
+ try {
+ blockingStub.write(writeRequest(updates));
+ return true;
+ } catch (StatusRuntimeException e) {
+ checkAndLogWriteErrors(writeEntities, e, opType, entryType);
+ return false;
+ }
+ }
+
+ private WriteRequest writeRequest(Iterable<Update> updateMsgs) {
+ return WriteRequest.newBuilder()
.setDeviceId(p4DeviceId)
.setElectionId(clientElectionId)
.addAllUpdates(updateMsgs)
.build();
- try {
- blockingStub.write(writeRequestBuilder.build());
- return true;
- } catch (StatusRuntimeException e) {
- log.warn("Unable to write meter entries : {}", e.getMessage());
- log.debug("exception", e);
- return false;
- }
}
private Void doShutdown() {
@@ -926,7 +986,7 @@
errors.stream()
.filter(err -> err.getCanonicalCode() != Status.OK.getCode().value())
.forEach(err -> log.warn("Unable to {} {} (unknown): {}",
- opType.name(), entryType, parseP4Error(err)));
+ opType.name(), entryType, parseP4Error(err)));
}
}
diff --git a/tools/dev/p4vm/bm-commands.sh b/tools/dev/p4vm/bm-commands.sh
index fdb8340..599f5c0 100644
--- a/tools/dev/p4vm/bm-commands.sh
+++ b/tools/dev/p4vm/bm-commands.sh
@@ -7,7 +7,7 @@
fi
tport=$(head -n 1 /tmp/bmv2-$1-thrift-port)
echo "Starting CLI for BMv2 instance $1 (Thrift port $tport)..."
- sudo bm_CLI --thrift-port ${tport} ${@:2}
+ sudo bm_CLI --thrift-port ${tport} --pre SimplePreLAG ${@:2}
}
bm-dbg () {