More robust P4Runtime group handling
This patch solves the PENDING_UPDATE and PENDING_ADD_RETRY issue
observed on the ONS EU topology.
The P4Runtime action profile group handling has been re-implemented to
be robust against inconsistencies of the device mirror, which is now
periodically synchronized with the device state. Similarly, we implement
a routine in the P4RuntimeClient to cleanup unused action profile
members.
This patch includes also:
- Refactor PI handle classes to allow creating handles without the
entity instance
- Use list instead of collections in P4RuntimeClient methods, as order
of updates sent and/or entities received from the device is important
Change-Id: I2e7964ce90f43d66680131b47ab52aca32ab55d2
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupHandle.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupHandle.java
index 4c87f1f..6969714 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupHandle.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupHandle.java
@@ -20,6 +20,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.model.PiActionProfileId;
/**
* Global identifier of a PI action group applied to a device, uniquely defined
@@ -28,8 +29,13 @@
@Beta
public final class PiActionGroupHandle extends PiHandle<PiActionGroup> {
+ private final PiActionProfileId actionProfileId;
+ private final PiActionGroupId groupId;
+
private PiActionGroupHandle(DeviceId deviceId, PiActionGroup group) {
- super(deviceId, group);
+ super(deviceId);
+ actionProfileId = group.actionProfileId();
+ groupId = group.id();
}
/**
@@ -45,10 +51,15 @@
}
@Override
+ public PiEntityType entityType() {
+ return PiEntityType.GROUP;
+ }
+
+ @Override
public int hashCode() {
return Objects.hashCode(deviceId(),
- piEntity().actionProfileId(),
- piEntity().id());
+ actionProfileId,
+ groupId);
}
@Override
@@ -61,17 +72,17 @@
}
PiActionGroupHandle that = (PiActionGroupHandle) o;
return Objects.equal(deviceId(), that.deviceId()) &&
- Objects.equal(piEntity().actionProfileId(),
- that.piEntity().actionProfileId()) &&
- Objects.equal(piEntity().id(), that.piEntity().id());
+ Objects.equal(actionProfileId,
+ that.actionProfileId) &&
+ Objects.equal(groupId, that.groupId);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("deviceId", deviceId())
- .add("actionProfileId", piEntity().actionProfileId())
- .add("groupId", piEntity().id())
+ .add("actionProfileId", actionProfileId)
+ .add("groupId", groupId)
.toString();
}
}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMember.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMember.java
index 1c5ecb3..690d118 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMember.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMember.java
@@ -19,6 +19,7 @@
import com.google.common.annotations.Beta;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
+import org.onosproject.net.pi.model.PiActionProfileId;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -28,11 +29,18 @@
@Beta
public final class PiActionGroupMember implements PiEntity {
+ private final PiActionProfileId actionProfileId;
private final PiActionGroupMemberId id;
private final PiAction action;
+ // FIXME: in P4Runtime weight is an attribute of the member reference in a
+ // group. Either remove it from this class or define the containing group
+ // ID.
private final int weight;
- private PiActionGroupMember(PiActionGroupMemberId id, PiAction action, int weight) {
+ private PiActionGroupMember(
+ PiActionProfileId actionProfileId, PiActionGroupMemberId id,
+ PiAction action, int weight) {
+ this.actionProfileId = actionProfileId;
this.id = id;
this.action = action;
this.weight = weight;
@@ -48,6 +56,15 @@
}
/**
+ * Returns the identifier of the action profile.
+ *
+ * @return action profile identifier
+ */
+ public PiActionProfileId actionProfile() {
+ return actionProfileId;
+ }
+
+ /**
* Returns the action associated to this member.
*
* @return action
@@ -80,18 +97,20 @@
}
PiActionGroupMember that = (PiActionGroupMember) o;
return weight == that.weight &&
+ Objects.equal(actionProfileId, that.actionProfileId) &&
Objects.equal(id, that.id) &&
Objects.equal(action, that.action);
}
@Override
public int hashCode() {
- return Objects.hashCode(id, action, weight);
+ return Objects.hashCode(actionProfileId, id, action, weight);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
+ .add("actionProfile", actionProfileId)
.add("id", id)
.add("action", action)
.add("weight", weight)
@@ -112,6 +131,7 @@
*/
public static final class Builder {
+ private PiActionProfileId actionProfileId;
private PiActionGroupMemberId id;
private PiAction action;
private int weight;
@@ -121,6 +141,17 @@
}
/**
+ * Sets the action profile identifier of this member.
+ *
+ * @param actionProfileId action profile identifier
+ * @return this
+ */
+ public Builder forActionProfile(PiActionProfileId actionProfileId) {
+ this.actionProfileId = actionProfileId;
+ return this;
+ }
+
+ /**
* Sets the identifier of this member.
*
* @param id member identifier
@@ -161,9 +192,10 @@
* @return action group member
*/
public PiActionGroupMember build() {
+ checkNotNull(actionProfileId);
checkNotNull(id);
checkNotNull(action);
- return new PiActionGroupMember(id, action, weight);
+ return new PiActionGroupMember(actionProfileId, id, action, weight);
}
}
}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMemberHandle.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMemberHandle.java
new file mode 100644
index 0000000..9ef8a31
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMemberHandle.java
@@ -0,0 +1,126 @@
+/*
+ * 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.MoreObjects;
+import com.google.common.base.Objects;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.model.PiActionProfileId;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Global identifier of a PI action profile group member, uniquely defined by a
+ * device ID, action profile ID, and member ID.
+ */
+public final class PiActionGroupMemberHandle extends PiHandle<PiActionGroupMember> {
+
+ private final PiActionGroupMemberId memberId;
+ private final PiActionProfileId actionProfileId;
+
+ private PiActionGroupMemberHandle(DeviceId deviceId,
+ PiActionProfileId actionProfileId,
+ PiActionGroupMemberId memberId) {
+ super(deviceId);
+ this.actionProfileId = actionProfileId;
+ this.memberId = memberId;
+ }
+
+ /**
+ * Creates a new handle for the given device ID, action profile ID, and
+ * member ID.
+ *
+ * @param deviceId device ID
+ * @param actionProfileId action profile ID
+ * @param memberId member ID
+ * @return action profile group member handle
+ */
+ public static PiActionGroupMemberHandle of(
+ DeviceId deviceId,
+ PiActionProfileId actionProfileId,
+ PiActionGroupMemberId memberId) {
+ return new PiActionGroupMemberHandle(
+ deviceId, actionProfileId, memberId);
+ }
+
+ /**
+ * Creates a new handle for the given device ID, and action profile group
+ * member instance.
+ *
+ * @param deviceId device ID
+ * @param member member instance
+ * @return action profile group member handle
+ */
+ public static PiActionGroupMemberHandle of(
+ DeviceId deviceId,
+ PiActionGroupMember member) {
+ checkNotNull(member);
+ return new PiActionGroupMemberHandle(
+ deviceId, member.actionProfile(), member.id());
+ }
+
+ /**
+ * Returns the member ID of this handle.
+ *
+ * @return member ID
+ */
+ public PiActionGroupMemberId memberId() {
+ return memberId;
+ }
+
+ /**
+ * Returns the action profile ID of this handle.
+ *
+ * @return action profile ID
+ */
+ public PiActionProfileId actionProfileId() {
+ return actionProfileId;
+ }
+
+ @Override
+ public PiEntityType entityType() {
+ return PiEntityType.GROUP_MEMBER;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(deviceId(), actionProfileId, memberId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final PiActionGroupMemberHandle other = (PiActionGroupMemberHandle) obj;
+ return Objects.equal(this.deviceId(), other.deviceId())
+ && Objects.equal(this.actionProfileId, other.actionProfileId)
+ && Objects.equal(this.memberId, other.memberId);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("deviceId", deviceId())
+ .add("actionProfileId", actionProfileId)
+ .add("memberId", memberId)
+ .toString();
+ }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiHandle.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiHandle.java
index e8e70d1..eb74288 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiHandle.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiHandle.java
@@ -29,11 +29,9 @@
public abstract class PiHandle<E extends PiEntity> {
private final DeviceId deviceId;
- private final E piEntity;
- protected PiHandle(DeviceId deviceId, E piEntity) {
+ protected PiHandle(DeviceId deviceId) {
this.deviceId = checkNotNull(deviceId);
- this.piEntity = checkNotNull(piEntity);
}
/**
@@ -50,18 +48,7 @@
*
* @return PI entity type
*/
- public final PiEntityType entityType() {
- return piEntity.piEntityType();
- }
-
- /**
- * The entity to which this handle is associated.
- *
- * @return PI entity
- */
- public final E piEntity() {
- return piEntity;
- }
+ public abstract PiEntityType entityType();
@Override
public abstract int hashCode();
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterHandle.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterHandle.java
index ad2af9d..4baa6fa 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterHandle.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterHandle.java
@@ -21,33 +21,56 @@
import com.google.common.base.Objects;
import org.onosproject.net.DeviceId;
+import static com.google.common.base.Preconditions.checkNotNull;
+
/**
- * Global identifier of a PI meter cell configuration applied to a device, uniquely defined
- * by a device ID and meter cell ID.
+ * Global identifier of a PI meter cell configuration applied to a device,
+ * uniquely defined by a device ID and meter cell ID.
*/
@Beta
public final class PiMeterHandle extends PiHandle<PiMeterCellConfig> {
- private PiMeterHandle(DeviceId deviceId, PiMeterCellConfig meterCellConfig) {
- super(deviceId, meterCellConfig);
+ private final PiMeterCellId cellId;
+
+ private PiMeterHandle(DeviceId deviceId, PiMeterCellId meterCellId) {
+ super(deviceId);
+ this.cellId = meterCellId;
}
/**
- * Creates a new handle for the given device ID and PI meter cell configuration.
+ * Creates a new handle for the given device ID and PI meter cell ID.
*
- * @param deviceId device ID
+ * @param deviceId device ID
+ * @param meterCellId meter cell ID
+ * @return PI meter handle
+ */
+ public static PiMeterHandle of(DeviceId deviceId,
+ PiMeterCellId meterCellId) {
+ return new PiMeterHandle(deviceId, meterCellId);
+ }
+
+ /**
+ * Creates a new handle for the given device ID and PI meter cell
+ * configuration.
+ *
+ * @param deviceId device ID
* @param meterCellConfig meter config
* @return PI meter handle
*/
public static PiMeterHandle of(DeviceId deviceId,
PiMeterCellConfig meterCellConfig) {
- return new PiMeterHandle(deviceId, meterCellConfig);
+ checkNotNull(meterCellConfig);
+ return new PiMeterHandle(deviceId, meterCellConfig.cellId());
+ }
+
+ @Override
+ public PiEntityType entityType() {
+ return PiEntityType.METER_CELL_CONFIG;
}
@Override
public int hashCode() {
- return Objects.hashCode(deviceId(),
- piEntity().cellId());
+ return Objects.hashCode(deviceId(), cellId);
}
@Override
@@ -60,15 +83,14 @@
}
PiMeterHandle that = (PiMeterHandle) o;
return Objects.equal(deviceId(), that.deviceId()) &&
- Objects.equal(piEntity().cellId(),
- that.piEntity().cellId());
+ Objects.equal(cellId, that.cellId);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("deviceId", deviceId())
- .add("meterCellId", piEntity().cellId())
+ .add("meterCellId", cellId)
.toString();
}
-}
\ No newline at end of file
+}
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
index f9b1170..65a3f28 100644
--- 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
@@ -21,6 +21,8 @@
import com.google.common.base.Objects;
import org.onosproject.net.DeviceId;
+import static com.google.common.base.Preconditions.checkNotNull;
+
/**
* 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
@@ -29,8 +31,23 @@
@Beta
public final class PiMulticastGroupEntryHandle extends PiHandle<PiMulticastGroupEntry> {
- private PiMulticastGroupEntryHandle(DeviceId deviceId, PiMulticastGroupEntry entry) {
- super(deviceId, entry);
+ private final long groupId;
+
+ private PiMulticastGroupEntryHandle(DeviceId deviceId, long groupId) {
+ super(deviceId);
+ this.groupId = groupId;
+ }
+
+ /**
+ * Creates a new handle for the given device ID and PI multicast group ID.
+ *
+ * @param deviceId device ID
+ * @param groupId multicast group ID
+ * @return PI multicast group entry handle
+ */
+ public static PiMulticastGroupEntryHandle of(DeviceId deviceId,
+ long groupId) {
+ return new PiMulticastGroupEntryHandle(deviceId, groupId);
}
/**
@@ -43,12 +60,18 @@
*/
public static PiMulticastGroupEntryHandle of(DeviceId deviceId,
PiMulticastGroupEntry entry) {
- return new PiMulticastGroupEntryHandle(deviceId, entry);
+ checkNotNull(entry);
+ return new PiMulticastGroupEntryHandle(deviceId, entry.groupId());
+ }
+
+ @Override
+ public PiEntityType entityType() {
+ return PiEntityType.PRE_MULTICAST_GROUP_ENTRY;
}
@Override
public int hashCode() {
- return Objects.hashCode(deviceId(), piEntity().groupId());
+ return Objects.hashCode(deviceId(), groupId);
}
@Override
@@ -61,14 +84,14 @@
}
PiMulticastGroupEntryHandle that = (PiMulticastGroupEntryHandle) o;
return Objects.equal(deviceId(), that.deviceId()) &&
- Objects.equal(piEntity().groupId(), that.piEntity().groupId());
+ Objects.equal(groupId, that.groupId);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("deviceId", deviceId())
- .add("groupId", piEntity().groupId())
+ .add("groupId", groupId)
.toString();
}
}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
index 04b2528..f8a1460 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
@@ -135,6 +135,7 @@
}
PiTableEntry that = (PiTableEntry) o;
return priority == that.priority &&
+ cookie == that.cookie &&
Double.compare(that.timeout, timeout) == 0 &&
Objects.equal(tableId, that.tableId) &&
Objects.equal(matchKey, that.matchKey) &&
@@ -145,7 +146,7 @@
@Override
public int hashCode() {
return Objects.hashCode(tableId, matchKey, isDefaultAction, tableAction,
- priority, timeout);
+ priority, cookie, timeout);
}
@Override
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntryHandle.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntryHandle.java
index 7eeb7f6..2b210a1 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntryHandle.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntryHandle.java
@@ -20,6 +20,9 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.model.PiTableId;
+
+import static com.google.common.base.Preconditions.checkNotNull;
/**
* Global identifier of a PI table entry applied on a device, uniquely defined
@@ -28,8 +31,28 @@
@Beta
public final class PiTableEntryHandle extends PiHandle<PiTableEntry> {
- private PiTableEntryHandle(DeviceId deviceId, PiTableEntry entry) {
- super(deviceId, entry);
+ private final PiTableId tableId;
+ private final PiMatchKey matchKey;
+
+ private PiTableEntryHandle(DeviceId deviceId, PiTableId tableId, PiMatchKey matchKey) {
+ super(deviceId);
+ this.tableId = tableId;
+ this.matchKey = matchKey;
+ }
+
+ /**
+ * Creates a new handle for the given device ID, PI table ID, and match
+ * key.
+ *
+ * @param deviceId device ID
+ * @param tableId table ID
+ * @param matchKey match key
+ * @return PI table entry handle
+ */
+ public static PiTableEntryHandle of(DeviceId deviceId, PiTableId tableId, PiMatchKey matchKey) {
+ checkNotNull(tableId);
+ checkNotNull(matchKey);
+ return new PiTableEntryHandle(deviceId, tableId, matchKey);
}
/**
@@ -40,14 +63,18 @@
* @return PI table entry handle
*/
public static PiTableEntryHandle of(DeviceId deviceId, PiTableEntry entry) {
- return new PiTableEntryHandle(deviceId, entry);
+ checkNotNull(entry);
+ return PiTableEntryHandle.of(deviceId, entry.table(), entry.matchKey());
+ }
+
+ @Override
+ public PiEntityType entityType() {
+ return PiEntityType.TABLE_ENTRY;
}
@Override
public int hashCode() {
- return Objects.hashCode(deviceId(),
- piEntity().table(),
- piEntity().matchKey());
+ return Objects.hashCode(deviceId(), tableId, matchKey);
}
@Override
@@ -60,18 +87,16 @@
}
final PiTableEntryHandle other = (PiTableEntryHandle) obj;
return Objects.equal(this.deviceId(), other.deviceId())
- && Objects.equal(this.piEntity().table(),
- other.piEntity().table())
- && Objects.equal(this.piEntity().matchKey(),
- other.piEntity().matchKey());
+ && Objects.equal(this.tableId, other.tableId)
+ && Objects.equal(this.matchKey, other.matchKey);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("deviceId", deviceId())
- .add("tableId", piEntity().table())
- .add("matchKey", piEntity().matchKey())
+ .add("tableId", tableId)
+ .add("matchKey", matchKey)
.toString();
}
}
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupMemberTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupMemberTest.java
index 020d575..e84d48f 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupMemberTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupMemberTest.java
@@ -20,6 +20,7 @@
import org.junit.Test;
import org.onosproject.net.pi.model.PiActionId;
import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiActionProfileId;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -34,26 +35,37 @@
*/
public class PiActionGroupMemberTest {
+ private final PiActionProfileId actionProfileId1 = PiActionProfileId.of("foo");
+ private final PiActionProfileId actionProfileId2 = PiActionProfileId.of("bar");
private final PiActionGroupMemberId piActionGroupMemberId = PiActionGroupMemberId.of(10);
private final PiAction piAction = PiAction.builder().withId(PiActionId.of(MOD_NW_DST))
.withParameter(new PiActionParam(PiActionParamId.of(DST_ADDR), copyFrom(0x0a010101)))
.build();
private final PiActionGroupMember piActionGroupMember1 = PiActionGroupMember.builder()
+ .forActionProfile(actionProfileId1)
.withId(piActionGroupMemberId)
.withAction(piAction)
.withWeight(10)
.build();
private final PiActionGroupMember sameAsPiActionGroupMember1 = PiActionGroupMember.builder()
+ .forActionProfile(actionProfileId1)
.withId(piActionGroupMemberId)
.withAction(piAction)
.withWeight(10)
.build();
private final PiActionGroupMember piActionGroupMember2 = PiActionGroupMember.builder()
+ .forActionProfile(actionProfileId1)
.withId(piActionGroupMemberId)
.withAction(piAction)
.withWeight(20)
.build();
+ private final PiActionGroupMember piActionGroupMember1ForOtherProfile = PiActionGroupMember.builder()
+ .forActionProfile(actionProfileId2)
+ .withId(piActionGroupMemberId)
+ .withAction(piAction)
+ .withWeight(10)
+ .build();
/**
* Checks that the PiActionGroupMember class is immutable.
@@ -73,6 +85,7 @@
new EqualsTester()
.addEqualityGroup(piActionGroupMember1, sameAsPiActionGroupMember1)
.addEqualityGroup(piActionGroupMember2)
+ .addEqualityGroup(piActionGroupMember1ForOtherProfile)
.testEquals();
}
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java
index bd4bc2c..c3aca5a 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java
@@ -45,6 +45,7 @@
.build();
private final PiActionGroupMember piActionGroupMember = PiActionGroupMember.builder()
+ .forActionProfile(ACTION_PROF_ID)
.withId(piActionGroupMemberId)
.withAction(piAction)
.withWeight(10)
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiGroupTranslatorImpl.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiGroupTranslatorImpl.java
index 70d2dde..d989571 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiGroupTranslatorImpl.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiGroupTranslatorImpl.java
@@ -116,6 +116,7 @@
}
piActionGroupBuilder.addMember(PiActionGroupMember.builder()
+ .forActionProfile(groupKey.actionProfileId())
.withId(PiActionGroupMemberId.of(memberId))
.withAction((PiAction) tableAction)
.withWeight(bucket.weight())
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
index aeaa553..6c86604 100644
--- 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
@@ -109,6 +109,7 @@
.withId(ACT_SET_EGRESS_PORT_WCMP_ID)
.withParameter(param).build();
return PiActionGroupMember.builder()
+ .forActionProfile(ACT_PRF_WCMP_SELECTOR_ID)
.withAction(piAction)
.withId(PiActionGroupMemberId.of(BASE_MEM_ID + portNum))
.withWeight(DEFAULT_MEMBER_WEIGHT)
diff --git a/core/store/dist/src/test/java/org/onosproject/store/pi/impl/DistributedPiTranslationStoreTest.java b/core/store/dist/src/test/java/org/onosproject/store/pi/impl/DistributedPiTranslationStoreTest.java
index 2ba52a2..acfce12 100644
--- a/core/store/dist/src/test/java/org/onosproject/store/pi/impl/DistributedPiTranslationStoreTest.java
+++ b/core/store/dist/src/test/java/org/onosproject/store/pi/impl/DistributedPiTranslationStoreTest.java
@@ -45,7 +45,12 @@
};
private static final PiEntity PI_ENTITY = () -> PiEntityType.TABLE_ENTRY;
private static final PiHandle<PiEntity> PI_HANDLE =
- new PiHandle<PiEntity>(DeviceId.NONE, PI_ENTITY) {
+ new PiHandle<PiEntity>(DeviceId.NONE) {
+ @Override
+ public PiEntityType entityType() {
+ return PI_ENTITY.piEntityType();
+ }
+
@Override
public int hashCode() {
return HANDLE_HASH;
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index f57421f..0e684fe 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -109,10 +109,6 @@
import org.onosproject.net.flow.StoredFlowEntry;
import org.onosproject.net.flow.TableId;
import org.onosproject.net.flow.TableStatisticsEntry;
-import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
-import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
-import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
-import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
import org.onosproject.net.flow.criteria.ArpHaCriterion;
import org.onosproject.net.flow.criteria.ArpOpCriterion;
import org.onosproject.net.flow.criteria.ArpPaCriterion;
@@ -157,6 +153,10 @@
import org.onosproject.net.flow.instructions.L3ModificationInstruction;
import org.onosproject.net.flow.instructions.L4ModificationInstruction;
import org.onosproject.net.flow.instructions.PiInstruction;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEntry;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchEvent;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchOperation;
+import org.onosproject.net.flow.oldbatch.FlowRuleBatchRequest;
import org.onosproject.net.flowobjective.DefaultFilteringObjective;
import org.onosproject.net.flowobjective.DefaultForwardingObjective;
import org.onosproject.net.flowobjective.DefaultNextObjective;
@@ -227,6 +227,7 @@
import org.onosproject.net.pi.runtime.PiActionGroupHandle;
import org.onosproject.net.pi.runtime.PiActionGroupId;
import org.onosproject.net.pi.runtime.PiActionGroupMember;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberHandle;
import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
import org.onosproject.net.pi.runtime.PiActionParam;
import org.onosproject.net.pi.runtime.PiControlMetadata;
@@ -242,12 +243,12 @@
import org.onosproject.net.pi.runtime.PiMatchKey;
import org.onosproject.net.pi.runtime.PiMeterCellId;
import org.onosproject.net.pi.runtime.PiPacketOperation;
-import org.onosproject.net.pi.service.PiPipeconfConfig;
import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
import org.onosproject.net.pi.runtime.PiTableAction;
import org.onosproject.net.pi.runtime.PiTableEntry;
-import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
import org.onosproject.net.pi.runtime.PiTableEntryHandle;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+import org.onosproject.net.pi.service.PiPipeconfConfig;
import org.onosproject.net.pi.service.PiTranslatable;
import org.onosproject.net.pi.service.PiTranslatedEntity;
import org.onosproject.net.provider.ProviderId;
@@ -687,6 +688,7 @@
PiActionGroupHandle.class,
PiActionGroupId.class,
PiActionGroupMember.class,
+ PiActionGroupMemberHandle.class,
PiActionGroupMemberId.class,
PiActionParam.class,
PiControlMetadata.class,
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeActionGroupProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeActionGroupProgrammable.java
new file mode 100644
index 0000000..5ee965d
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeActionGroupProgrammable.java
@@ -0,0 +1,436 @@
+/*
+ * 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.drivers.p4runtime;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Striped;
+import org.onlab.util.SharedExecutors;
+import org.onosproject.drivers.p4runtime.mirror.P4RuntimeActionProfileMemberMirror;
+import org.onosproject.drivers.p4runtime.mirror.P4RuntimeGroupMirror;
+import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupDescription;
+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;
+import org.onosproject.net.group.GroupStore;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiActionProfileModel;
+import org.onosproject.net.pi.runtime.PiAction;
+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.PiActionGroupMemberHandle;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import org.onosproject.net.pi.service.PiGroupTranslator;
+import org.onosproject.net.pi.service.PiTranslatedEntity;
+import org.onosproject.net.pi.service.PiTranslationException;
+import org.onosproject.p4runtime.api.P4RuntimeClient;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.MODIFY;
+
+/**
+ * Implementation of GroupProgrammable to handle action profile groups in
+ * P4Runtime.
+ */
+public class P4RuntimeActionGroupProgrammable
+ extends AbstractP4RuntimeHandlerBehaviour
+ implements GroupProgrammable {
+
+ // If true, we avoid querying the device and return what's already known by
+ // the ONOS store.
+ private static final String READ_ACTION_GROUPS_FROM_MIRROR = "actionGroupReadFromMirror";
+ private static final boolean DEFAULT_READ_ACTION_GROUPS_FROM_MIRROR = false;
+
+ protected GroupStore groupStore;
+ private P4RuntimeGroupMirror groupMirror;
+ private P4RuntimeActionProfileMemberMirror memberMirror;
+ private PiGroupTranslator groupTranslator;
+
+ // Needed to synchronize operations over the same group.
+ private static final Striped<Lock> STRIPED_LOCKS = Striped.lock(30);
+
+ @Override
+ protected boolean setupBehaviour() {
+ if (!super.setupBehaviour()) {
+ return false;
+ }
+ groupMirror = this.handler().get(P4RuntimeGroupMirror.class);
+ memberMirror = this.handler().get(P4RuntimeActionProfileMemberMirror.class);
+ groupStore = handler().get(GroupStore.class);
+ groupTranslator = piTranslationService.groupTranslator();
+ return true;
+ }
+
+ @Override
+ public void performGroupOperation(DeviceId deviceId,
+ GroupOperations groupOps) {
+ if (!setupBehaviour()) {
+ return;
+ }
+
+ groupOps.operations().stream()
+ .filter(op -> !op.groupType().equals(GroupDescription.Type.ALL))
+ .forEach(op -> {
+ // ONOS-7785 We need app cookie (action profile id) from the group
+ Group groupOnStore = groupStore.getGroup(deviceId, op.groupId());
+ GroupDescription groupDesc = new DefaultGroupDescription(
+ deviceId, op.groupType(), op.buckets(), groupOnStore.appCookie(),
+ op.groupId().id(), groupOnStore.appId());
+ DefaultGroup groupToApply = new DefaultGroup(op.groupId(), groupDesc);
+ processGroupOperation(groupToApply, op.opType());
+ });
+ }
+
+ @Override
+ public Collection<Group> getGroups() {
+ if (!setupBehaviour()) {
+ return Collections.emptyList();
+ }
+ return getActionGroups();
+ }
+
+ private Collection<Group> getActionGroups() {
+
+ if (driverBoolProperty(READ_ACTION_GROUPS_FROM_MIRROR,
+ DEFAULT_READ_ACTION_GROUPS_FROM_MIRROR)) {
+ return getActionGroupsFromMirror();
+ }
+
+ final Collection<PiActionProfileId> actionProfileIds = pipeconf.pipelineModel()
+ .actionProfiles()
+ .stream()
+ .map(PiActionProfileModel::id)
+ .collect(Collectors.toList());
+ final List<PiActionGroup> groupsOnDevice = actionProfileIds.stream()
+ .flatMap(this::streamGroupsFromDevice)
+ .collect(Collectors.toList());
+ final Set<PiActionGroupMemberHandle> membersOnDevice = actionProfileIds
+ .stream()
+ .flatMap(actProfId -> getMembersFromDevice(actProfId)
+ .stream()
+ .map(memberId -> PiActionGroupMemberHandle.of(
+ deviceId, actProfId, memberId)))
+ .collect(Collectors.toSet());
+
+ if (groupsOnDevice.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ // Sync mirrors.
+ syncGroupMirror(groupsOnDevice);
+ syncMemberMirror(membersOnDevice);
+
+ final List<Group> result = Lists.newArrayList();
+ final List<PiActionGroup> inconsistentGroups = Lists.newArrayList();
+ final List<PiActionGroup> validGroups = Lists.newArrayList();
+
+ for (PiActionGroup piGroup : groupsOnDevice) {
+ final Group pdGroup = forgeGroupEntry(piGroup);
+ if (pdGroup == null) {
+ // Entry is on device but unknown to translation service or
+ // device mirror. Inconsistent. Mark for removal.
+ inconsistentGroups.add(piGroup);
+ } else {
+ validGroups.add(piGroup);
+ result.add(pdGroup);
+ }
+ }
+
+ // Trigger clean up of inconsistent groups and members. This will also
+ // remove all members that are not used by any group, and update the
+ // mirror accordingly.
+ final Set<PiActionGroupMemberHandle> membersToKeep = validGroups.stream()
+ .flatMap(g -> g.members().stream())
+ .map(m -> PiActionGroupMemberHandle.of(deviceId, m))
+ .collect(Collectors.toSet());
+ final Set<PiActionGroupMemberHandle> inconsistentMembers = Sets.difference(
+ membersOnDevice, membersToKeep);
+ SharedExecutors.getSingleThreadExecutor().execute(
+ () -> cleanUpInconsistentGroupsAndMembers(
+ inconsistentGroups, inconsistentMembers));
+
+ return result;
+ }
+
+ private void syncGroupMirror(Collection<PiActionGroup> groups) {
+ Map<PiActionGroupHandle, PiActionGroup> handleMap = Maps.newHashMap();
+ groups.forEach(g -> handleMap.put(PiActionGroupHandle.of(deviceId, g), g));
+ groupMirror.sync(deviceId, handleMap);
+ }
+
+ private void syncMemberMirror(Collection<PiActionGroupMemberHandle> memberHandles) {
+ Map<PiActionGroupMemberHandle, PiActionGroupMember> handleMap = Maps.newHashMap();
+ memberHandles.forEach(handle -> handleMap.put(
+ handle, dummyMember(handle.actionProfileId(), handle.memberId())));
+ memberMirror.sync(deviceId, handleMap);
+ }
+
+ private Collection<Group> getActionGroupsFromMirror() {
+ return groupMirror.getAll(deviceId).stream()
+ .map(TimedEntry::entry)
+ .map(this::forgeGroupEntry)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ private void cleanUpInconsistentGroupsAndMembers(Collection<PiActionGroup> groupsToRemove,
+ Collection<PiActionGroupMemberHandle> membersToRemove) {
+ if (!groupsToRemove.isEmpty()) {
+ log.warn("Found {} inconsistent action profile groups on {}, removing them...",
+ groupsToRemove.size(), deviceId);
+ groupsToRemove.forEach(piGroup -> {
+ log.debug(piGroup.toString());
+ processGroup(piGroup, null, Operation.REMOVE);
+ });
+ }
+ if (!membersToRemove.isEmpty()) {
+ log.warn("Found {} inconsistent action profile members on {}, removing them...",
+ membersToRemove.size(), deviceId);
+ // FIXME: implement client call to remove members from multiple
+ // action profiles in one shot.
+ final ListMultimap<PiActionProfileId, PiActionGroupMemberId>
+ membersByActProfId = ArrayListMultimap.create();
+ membersToRemove.forEach(m -> membersByActProfId.put(
+ m.actionProfileId(), m.memberId()));
+ membersByActProfId.keySet().forEach(actProfId -> {
+ List<PiActionGroupMemberId> removedMembers = getFutureWithDeadline(
+ client.removeActionProfileMembers(
+ actProfId, membersByActProfId.get(actProfId), pipeconf),
+ "cleaning up action profile members", Collections.emptyList());
+ // Update member mirror.
+ removedMembers.stream()
+ .map(id -> PiActionGroupMemberHandle.of(deviceId, actProfId, id))
+ .forEach(memberMirror::remove);
+ });
+ }
+ }
+
+ private Stream<PiActionGroup> streamGroupsFromDevice(PiActionProfileId actProfId) {
+ // TODO: implement P4Runtime client call to read all groups with one call
+ // Good if pipeline has multiple action profiles.
+ final Collection<PiActionGroup> groups = getFutureWithDeadline(
+ client.dumpGroups(actProfId, pipeconf),
+ "dumping groups", Collections.emptyList());
+ return groups.stream();
+ }
+
+ private List<PiActionGroupMemberId> getMembersFromDevice(PiActionProfileId actProfId) {
+ // TODO: implement P4Runtime client call to read all members with one call
+ // Good if pipeline has multiple action profiles.
+ return getFutureWithDeadline(
+ client.dumpActionProfileMemberIds(actProfId, pipeconf),
+ "dumping action profile ids", Collections.emptyList());
+ }
+
+ private Group forgeGroupEntry(PiActionGroup piGroup) {
+ final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup);
+ final Optional<PiTranslatedEntity<Group, PiActionGroup>>
+ translatedEntity = groupTranslator.lookup(handle);
+ final TimedEntry<PiActionGroup> timedEntry = groupMirror.get(handle);
+ // Is entry consistent with our state?
+ if (!translatedEntity.isPresent()) {
+ log.warn("Group handle not found in translation store: {}", handle);
+ return null;
+ }
+ if (!translatedEntity.get().translated().equals(piGroup)) {
+ log.warn("Group obtained from device {} is different from the one in" +
+ "translation store: device={}, store={}",
+ deviceId, piGroup, translatedEntity.get().translated());
+ return null;
+ }
+ if (timedEntry == null) {
+ log.warn("Group handle not found in device mirror: {}", handle);
+ return null;
+ }
+ return addedGroup(translatedEntity.get().original(), timedEntry.lifeSec());
+ }
+
+ private Group addedGroup(Group original, long life) {
+ final DefaultGroup forgedGroup = new DefaultGroup(original.id(), original);
+ forgedGroup.setState(Group.GroupState.ADDED);
+ forgedGroup.setLife(life);
+ return forgedGroup;
+ }
+
+ private void processGroupOperation(Group pdGroup, GroupOperation.Type opType) {
+ final PiActionGroup piGroup;
+ try {
+ piGroup = groupTranslator.translate(pdGroup, pipeconf);
+ } catch (PiTranslationException e) {
+ log.warn("Unable to translate group, aborting {} operation: {} [{}]",
+ opType, e.getMessage(), pdGroup);
+ return;
+ }
+ final Operation operation = opType.equals(GroupOperation.Type.DELETE)
+ ? Operation.REMOVE : Operation.APPLY;
+ processGroup(piGroup, pdGroup, operation);
+ }
+
+ private void processGroup(PiActionGroup groupToApply,
+ Group pdGroup,
+ Operation operation) {
+ final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, groupToApply);
+ STRIPED_LOCKS.get(handle).lock();
+ try {
+ switch (operation) {
+ case APPLY:
+ if (applyGroupWithMembersOrNothing(groupToApply, handle)) {
+ groupTranslator.learn(handle, new PiTranslatedEntity<>(
+ pdGroup, groupToApply, handle));
+ }
+ return;
+ case REMOVE:
+ if (deleteGroup(groupToApply, handle)) {
+ groupTranslator.forget(handle);
+ }
+ return;
+ default:
+ log.error("Unknwon group operation type {}, cannot process group", operation);
+ break;
+ }
+ } finally {
+ STRIPED_LOCKS.get(handle).unlock();
+ }
+ }
+
+ private boolean applyGroupWithMembersOrNothing(PiActionGroup group, PiActionGroupHandle handle) {
+ // First apply members, then group, if fails, delete members.
+ if (!applyAllMembersOrNothing(group.members())) {
+ return false;
+ }
+ if (!applyGroup(group, handle)) {
+ deleteMembers(group.members());
+ return false;
+ }
+ return true;
+ }
+
+ private boolean applyGroup(PiActionGroup group, PiActionGroupHandle handle) {
+ final P4RuntimeClient.WriteOperationType opType =
+ groupMirror.get(handle) == null ? INSERT : MODIFY;
+ final boolean success = getFutureWithDeadline(
+ client.writeActionGroup(group, opType, pipeconf),
+ "performing action profile group " + opType, false);
+ if (success) {
+ groupMirror.put(handle, group);
+ }
+ return success;
+ }
+
+ private boolean deleteGroup(PiActionGroup group, PiActionGroupHandle handle) {
+ final boolean success = getFutureWithDeadline(
+ client.writeActionGroup(group, DELETE, pipeconf),
+ "performing action profile group " + DELETE, false);
+ if (success) {
+ groupMirror.remove(handle);
+ }
+ return success;
+ }
+
+ private boolean applyAllMembersOrNothing(Collection<PiActionGroupMember> members) {
+ Collection<PiActionGroupMember> appliedMembers = applyMembers(members);
+ if (appliedMembers.size() == members.size()) {
+ return true;
+ } else {
+ deleteMembers(appliedMembers);
+ return false;
+ }
+ }
+
+ private Collection<PiActionGroupMember> applyMembers(
+ Collection<PiActionGroupMember> members) {
+ return members.stream()
+ .filter(this::applyMember)
+ .collect(Collectors.toList());
+ }
+
+ private boolean applyMember(PiActionGroupMember member) {
+ // If exists, modify, otherwise insert
+ final PiActionGroupMemberHandle handle = PiActionGroupMemberHandle.of(
+ deviceId, member);
+ final P4RuntimeClient.WriteOperationType opType =
+ memberMirror.get(handle) == null ? INSERT : MODIFY;
+ final boolean success = getFutureWithDeadline(
+ client.writeActionGroupMembers(Collections.singletonList(member),
+ opType, pipeconf),
+ "performing action profile member " + opType, false);
+ if (success) {
+ memberMirror.put(handle, dummyMember(member.actionProfile(), member.id()));
+ }
+ return success;
+ }
+
+ private void deleteMembers(Collection<PiActionGroupMember> members) {
+ members.forEach(this::deleteMember);
+ }
+
+ private void deleteMember(PiActionGroupMember member) {
+ final PiActionGroupMemberHandle handle = PiActionGroupMemberHandle.of(
+ deviceId, member);
+ final boolean success = getFutureWithDeadline(
+ client.writeActionGroupMembers(Collections.singletonList(member),
+ DELETE, pipeconf),
+ "performing action profile member " + DELETE, false);
+ if (success) {
+ memberMirror.remove(handle);
+ }
+ }
+
+ // FIXME: this is nasty, we have to rely on a dummy member of the mirror
+ // because the PiActionGroupMember abstraction is broken, since it includes
+ // attributes that are not part of a P4Runtime member, e.g. weight.
+ // We should remove weight from the class, and have client methods that
+ // return the full PiActionGroupMember, not just the IDs. Also the naming
+ // "ActionGroupMember" is wrong since it makes believe that members can
+ // exists only inside a group, which is not true.
+ private PiActionGroupMember dummyMember(
+ PiActionProfileId actionProfileId, PiActionGroupMemberId memberId) {
+ return PiActionGroupMember.builder()
+ .forActionProfile(actionProfileId)
+ .withId(memberId)
+ .withAction(PiAction.builder()
+ .withId(PiActionId.of("dummy"))
+ .build())
+ .build();
+ }
+
+ enum Operation {
+ APPLY, REMOVE
+ }
+}
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
index 85a87fe..d0acdee 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
@@ -140,6 +140,11 @@
streamEntries(), streamDefaultEntries())
// Ignore entries from constant tables.
.filter(e -> !tableIsConstant(e.table()))
+ // Device implementation might return duplicate entries. For
+ // example if reading only default ones is not supported and
+ // non-default entries are returned, by using distinct() we are
+ // robust against that possibility.
+ .distinct()
.collect(Collectors.toList());
if (deviceEntries.isEmpty()) {
@@ -148,7 +153,6 @@
// Synchronize mirror with the device state.
syncMirror(deviceEntries);
- // Read table direct counters for non default-entries (if any).
// TODO: ONOS-7596 read counters with table entries
final Map<PiTableEntry, PiCounterCellData> counterCellMap =
readEntryCounters(deviceEntries);
@@ -229,7 +233,12 @@
log.warn("Table entry handle not found in translation store: {}", handle);
return null;
}
-
+ if (!translatedEntity.get().translated().equals(entry)) {
+ log.warn("Table entry obtained from device {} is different from " +
+ "one in in translation store: device={}, store={}",
+ deviceId, entry, translatedEntity.get().translated());
+ return null;
+ }
if (timedEntry == null) {
log.warn("Table entry handle not found in device mirror: {}", handle);
return null;
@@ -460,6 +469,7 @@
cellDatas = Collections.emptyList();
} else {
Set<PiCounterCellId> cellIds = tableEntries.stream()
+ // Ignore counter for default entry.
.filter(e -> !e.isDefaultAction())
.filter(e -> tableHasCounter(e.table()))
.map(PiCounterCellId::ofDirect)
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 f53dca8..84678dc 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-present Open Networking Foundation
+ * 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.
@@ -18,545 +18,92 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.Striped;
-import org.onlab.util.SharedExecutors;
-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.DefaultGroupDescription;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
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;
-import org.onosproject.net.group.GroupStore;
-import org.onosproject.net.pi.model.PiActionProfileId;
-import org.onosproject.net.pi.model.PiActionProfileModel;
-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.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.locks.Lock;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
import static com.google.common.base.Preconditions.checkArgument;
-import static java.lang.String.format;
-import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
-import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
-import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.MODIFY;
import static org.slf4j.LoggerFactory.getLogger;
/**
- * 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.
+ * Implementation of GroupProgrammable for P4Runtime devices that uses two
+ * different implementation of the same behavior to handle both action profile
+ * groups and multicast groups.
*/
public class P4RuntimeGroupProgrammable
- extends AbstractP4RuntimeHandlerBehaviour
- implements GroupProgrammable {
+ extends AbstractHandlerBehaviour implements GroupProgrammable {
- private static final String ACT_GRP_MEMS_STR = "action group members";
- private static final String DELETE_STR = "delete";
- private static final String ACT_GRP_STR = "action group";
- private static final String INSERT_STR = "insert";
- private static final String MODIFY_STR = "modify";
+ private final Logger log = getLogger(this.getClass());
- private static final Logger log = getLogger(P4RuntimeGroupProgrammable.class);
-
- // If true, we ignore re-installing groups that are already known in the
- // device mirror.
- private static final String CHECK_MIRROR_BEFORE_UPDATE = "checkMirrorBeforeUpdate";
- private static final boolean DEFAULT_CHECK_MIRROR_BEFORE_UPDATE = true;
-
- // If true, we avoid querying the device and return what's already known by
- // the ONOS store.
- private static final String READ_ACTION_GROUPS_FROM_MIRROR = "actionGroupReadFromMirror";
- private static final boolean DEFAULT_READ_ACTION_GROUPS_FROM_MIRROR = false;
-
- protected GroupStore groupStore;
- private P4RuntimeGroupMirror groupMirror;
- private PiGroupTranslator groupTranslator;
- private P4RuntimeMulticastGroupMirror mcGroupMirror;
- private PiMulticastGroupTranslator mcGroupTranslator;
-
- // Needed to synchronize operations over the same group.
- private static final Striped<Lock> STRIPED_LOCKS = Striped.lock(30);
-
- @Override
- protected boolean setupBehaviour() {
- if (!super.setupBehaviour()) {
- return false;
+ private void doPerformGroupOperation(DeviceId deviceId, GroupOperations groupOps) {
+ // TODO: fix GroupProgrammable API, passing the device ID is ambiguous
+ checkArgument(deviceId.equals(data().deviceId()),
+ "passed deviceId must be the same assigned to this behavior");
+ final List<GroupOperation> actionGroups = Lists.newArrayList();
+ final List<GroupOperation> multicastGroups = Lists.newArrayList();
+ groupOps.operations().forEach(op -> {
+ if (op.groupType().equals(GroupDescription.Type.ALL)) {
+ multicastGroups.add(op);
+ } else {
+ actionGroups.add(op);
+ }
+ });
+ if (!actionGroups.isEmpty()) {
+ actionProgrammable().performGroupOperation(
+ deviceId, new GroupOperations(actionGroups));
}
- groupMirror = this.handler().get(P4RuntimeGroupMirror.class);
- mcGroupMirror = this.handler().get(P4RuntimeMulticastGroupMirror.class);
- groupStore = handler().get(GroupStore.class);
- groupTranslator = piTranslationService.groupTranslator();
- mcGroupTranslator = piTranslationService.multicastGroupTranslator();
- return true;
+ if (!multicastGroups.isEmpty()) {
+ multicastProgrammable().performGroupOperation(
+ deviceId, new GroupOperations(multicastGroups));
+ }
+ }
+
+ private Collection<Group> doGetGroups() {
+ return new ImmutableList.Builder<Group>()
+ .addAll(actionProgrammable().getGroups())
+ .addAll(multicastProgrammable().getGroups())
+ .build();
+ }
+
+ private P4RuntimeActionGroupProgrammable actionProgrammable() {
+ P4RuntimeActionGroupProgrammable prog = new P4RuntimeActionGroupProgrammable();
+ prog.setData(data());
+ prog.setHandler(handler());
+ return prog;
+ }
+
+ private P4RuntimeMulticastGroupProgrammable multicastProgrammable() {
+ P4RuntimeMulticastGroupProgrammable prog = new P4RuntimeMulticastGroupProgrammable();
+ prog.setData(data());
+ prog.setHandler(handler());
+ return prog;
}
@Override
- public void performGroupOperation(DeviceId deviceId,
- GroupOperations groupOps) {
- if (!setupBehaviour()) {
- return;
+ public void performGroupOperation(DeviceId deviceId, GroupOperations groupOps) {
+ try {
+ doPerformGroupOperation(deviceId, groupOps);
+ } catch (Throwable ex) {
+ log.error("Unhandled exception on performGroupOperation", ex);
}
-
- // TODO: fix GroupProgrammable API, passing the device ID is ambiguous
- checkArgument(deviceId.equals(this.deviceId),
- "passed deviceId must be the same assigned to this behavior");
-
- groupOps.operations().forEach(op -> {
- // ONOS-7785 We need app cookie (action profile id) from the group
- Group groupOnStore = groupStore.getGroup(deviceId, op.groupId());
- GroupDescription groupDesc = new DefaultGroupDescription(deviceId,
- op.groupType(),
- op.buckets(),
- groupOnStore.appCookie(),
- op.groupId().id(),
- groupOnStore.appId());
- DefaultGroup groupToApply = new DefaultGroup(op.groupId(), groupDesc);
- if (op.groupType().equals(GroupDescription.Type.ALL)) {
- processMcGroupOp(groupToApply, op.opType());
- } else {
-
- processActionGroupOp(groupToApply, op.opType());
- }
- });
}
@Override
public Collection<Group> getGroups() {
- if (!setupBehaviour()) {
+ try {
+ return doGetGroups();
+ } catch (Throwable ex) {
+ log.error("Unhandled exception on getGroups", ex);
return Collections.emptyList();
}
- return new ImmutableList.Builder<Group>()
- .addAll(getActionGroups())
- .addAll(getMcGroups()).build();
- }
-
- private Collection<Group> getActionGroups() {
-
- if (driverBoolProperty(READ_ACTION_GROUPS_FROM_MIRROR,
- DEFAULT_READ_ACTION_GROUPS_FROM_MIRROR)) {
- return getActionGroupsFromMirror();
- }
-
- final Collection<PiActionGroup> piGroups = pipeconf.pipelineModel()
- .actionProfiles()
- .stream()
- .map(PiActionProfileModel::id)
- .flatMap(this::streamPiGroupsFromDevice)
- .collect(Collectors.toList());
-
- if (piGroups.isEmpty()) {
- return Collections.emptyList();
- }
-
- final List<Group> result = Lists.newArrayList();
- final List<PiActionGroup> inconsistentGroups = Lists.newArrayList();
-
- for (PiActionGroup piGroupOnDevice : piGroups) {
- final Group group = forgeGroupEntry(piGroupOnDevice);
- if (group == null) {
- // Entry is on device but unknown to translation service or
- // device mirror. Inconsistent. Mark for removal.
- inconsistentGroups.add(piGroupOnDevice);
- } else {
- result.add(group);
- }
- }
- // Trigger clean up of inconsistent entries (is any).
- // TODO: make this behaviour configurable, in some cases it's fine for
- // the device to have groups that were not installed by us.
- if (!inconsistentGroups.isEmpty()) {
- SharedExecutors.getSingleThreadExecutor().execute(
- () -> cleanUpInconsistentGroups(inconsistentGroups));
- }
- return result;
- }
-
- private Collection<Group> getActionGroupsFromMirror() {
- return groupMirror.getAll(deviceId).stream()
- .map(TimedEntry::entry)
- .map(this::forgeGroupEntry)
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
- }
-
- private void cleanUpInconsistentGroups(Collection<PiActionGroup> piGroups) {
- log.warn("Found {} inconsistent groups on {}, removing them...",
- piGroups.size(), deviceId);
- piGroups.forEach(piGroup -> {
- log.debug(piGroup.toString());
- // Per-piGroup lock.
- final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup);
- STRIPED_LOCKS.get(handle).lock();
- try {
- processActionGroup(handle, piGroup, null, null,
- GroupOperation.Type.DELETE);
- } finally {
- STRIPED_LOCKS.get(handle).unlock();
- }
- });
- }
-
- private Collection<Group> getMcGroups() {
- // TODO: missing support for reading multicast groups is ready in PI/Stratum.
- return getMcGroupsFromMirror();
- }
-
- private Collection<Group> getMcGroupsFromMirror() {
- return mcGroupMirror.getAll(deviceId).stream()
- .map(TimedEntry::entry)
- .map(this::forgeMcGroupEntry)
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
- }
-
- private void processActionGroupOp(Group pdGroup, GroupOperation.Type opType) {
- final PiActionGroup piGroup;
- try {
- piGroup = groupTranslator.translate(pdGroup, pipeconf);
- } catch (PiTranslationException e) {
- 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();
- // Per-piGroup lock.
- final Lock lock = STRIPED_LOCKS.get(handle);
- lock.lock();
- try {
- processActionGroup(handle, piGroup,
- groupOnDevice, pdGroup, opType);
- } finally {
- lock.unlock();
- }
- }
-
- private void processMcGroupOp(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();
- }
- }
-
- private void processActionGroup(PiActionGroupHandle handle,
- PiActionGroup groupToApply,
- PiActionGroup groupOnDevice,
- Group pdGroup, GroupOperation.Type operationType) {
- switch (operationType) {
- case ADD:
- if (groupOnDevice != null) {
- log.warn("Requested to ADD group {} on {}, but a group " +
- "with the same ID already exists, will " +
- "MODIFY instead",
- groupToApply.id(), deviceId);
- log.debug("To apply: {}", groupToApply);
- log.debug("On device: {}", groupOnDevice);
- processActionGroup(handle, groupToApply, groupOnDevice,
- pdGroup, GroupOperation.Type.MODIFY);
- return;
- }
- if (writeGroupToDevice(groupToApply)) {
- groupMirror.put(handle, groupToApply);
- groupTranslator.learn(handle, new PiTranslatedEntity<>(
- pdGroup, groupToApply, handle));
- }
- return;
- case MODIFY:
- if (groupOnDevice == null) {
- log.warn("Requested to MODIFY group {} on {}, but no " +
- "such group exists on the device, " +
- "will ADD instead",
- groupToApply.id(), deviceId);
- processActionGroup(handle, groupToApply, null,
- pdGroup, GroupOperation.Type.ADD);
- return;
- }
- if (driverBoolProperty(CHECK_MIRROR_BEFORE_UPDATE,
- DEFAULT_CHECK_MIRROR_BEFORE_UPDATE)
- && groupOnDevice.equals(groupToApply)) {
- // Group on device has the same members, ignore operation.
- return;
- }
- if (modifyGroupFromDevice(groupToApply, groupOnDevice)) {
- groupMirror.put(handle, groupToApply);
- groupTranslator.learn(handle, new PiTranslatedEntity<>(
- pdGroup, groupToApply, handle));
- }
- return;
- case DELETE:
- if (deleteGroupFromDevice(groupToApply)) {
- groupMirror.remove(handle);
- groupTranslator.forget(handle);
- }
- break;
- default:
- log.error("Unknwon group operation type {}, cannot process group", operationType);
- break;
- }
- }
-
- private void processMcGroup(PiMulticastGroupEntryHandle handle,
- PiMulticastGroupEntry groupToApply,
- PiMulticastGroupEntry groupOnDevice,
- Group pdGroup, GroupOperation.Type opType) {
- switch (opType) {
- case ADD:
- robustMcGroupAdd(handle, groupToApply, pdGroup);
- return;
- case MODIFY:
- // Since reading multicast groups is not supported yet on
- // PI/Stratum, we cannot trust groupOnDevic) as we don't have a
- // mechanism to enforce consistency of the mirror with the
- // device state.
- // if (driverBoolProperty(CHECK_MIRROR_BEFORE_UPDATE,
- // DEFAULT_CHECK_MIRROR_BEFORE_UPDATE)
- // && p4OpType == MODIFY
- // && groupOnDevice != null
- // && groupOnDevice.equals(groupToApply)) {
- // // Ignore.
- // return;
- // }
- robustMcGroupModify(handle, groupToApply, pdGroup);
- return;
- case DELETE:
- mcGroupApply(handle, groupToApply, pdGroup, DELETE);
- return;
- default:
- log.error("Unknown group operation type {}, " +
- "cannot process multicast group", opType);
- }
- }
-
- private boolean writeMcGroupOnDevice(PiMulticastGroupEntry group, P4RuntimeClient.WriteOperationType opType) {
- return getFutureWithDeadline(
- client.writePreMulticastGroupEntries(
- Collections.singleton(group), opType),
- "performing multicast group " + opType, false);
- }
-
- private boolean mcGroupApply(PiMulticastGroupEntryHandle handle,
- PiMulticastGroupEntry piGroup,
- Group pdGroup,
- P4RuntimeClient.WriteOperationType opType) {
- switch (opType) {
- case DELETE:
- if (writeMcGroupOnDevice(piGroup, DELETE)) {
- mcGroupMirror.remove(handle);
- mcGroupTranslator.forget(handle);
- return true;
- } else {
- return false;
- }
- case INSERT:
- case MODIFY:
- if (writeMcGroupOnDevice(piGroup, opType)) {
- mcGroupMirror.put(handle, piGroup);
- mcGroupTranslator.learn(handle, new PiTranslatedEntity<>(
- pdGroup, piGroup, handle));
- return true;
- } else {
- return false;
- }
- default:
- log.warn("Unknown operation type {}, cannot apply group", opType);
- return false;
- }
- }
-
- private void robustMcGroupAdd(PiMulticastGroupEntryHandle handle,
- PiMulticastGroupEntry piGroup,
- Group pdGroup) {
- if (mcGroupApply(handle, piGroup, pdGroup, INSERT)) {
- return;
- }
- // Try to delete (perhaps it already exists) and re-add...
- mcGroupApply(handle, piGroup, pdGroup, DELETE);
- mcGroupApply(handle, piGroup, pdGroup, INSERT);
- }
-
- private void robustMcGroupModify(PiMulticastGroupEntryHandle handle,
- PiMulticastGroupEntry piGroup,
- Group pdGroup) {
- if (mcGroupApply(handle, piGroup, pdGroup, MODIFY)) {
- return;
- }
- // Not sure for which reason it cannot be modified, so try to delete and insert instead...
- mcGroupApply(handle, piGroup, pdGroup, DELETE);
- mcGroupApply(handle, piGroup, pdGroup, INSERT);
- }
-
- private boolean modifyGroupFromDevice(PiActionGroup groupToApply, PiActionGroup groupOnDevice) {
- PiActionProfileId groupProfileId = groupToApply.actionProfileId();
- Collection<PiActionGroupMember> membersToRemove = Sets.newHashSet(groupOnDevice.members());
- membersToRemove.removeAll(groupToApply.members());
- Collection<PiActionGroupMember> membersToAdd = Sets.newHashSet(groupToApply.members());
- membersToAdd.removeAll(groupOnDevice.members());
-
- if (!membersToAdd.isEmpty() &&
- !completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, INSERT, pipeconf),
- ACT_GRP_MEMS_STR, INSERT_STR)) {
- // remove what we added
- completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, DELETE, pipeconf),
- ACT_GRP_MEMS_STR, INSERT_STR);
- return false;
- }
-
- if (!completeFuture(client.writeActionGroup(groupToApply, MODIFY, pipeconf),
- ACT_GRP_STR, MODIFY_STR)) {
- // recover group information
- completeFuture(client.writeActionGroup(groupOnDevice, MODIFY, pipeconf),
- ACT_GRP_STR, MODIFY_STR);
- // remove what we added
- completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, DELETE, pipeconf),
- ACT_GRP_MEMS_STR, INSERT_STR);
- return false;
- }
-
- if (!membersToRemove.isEmpty() &&
- !completeFuture(client.writeActionGroupMembers(groupProfileId, membersToRemove, DELETE, pipeconf),
- ACT_GRP_MEMS_STR, DELETE_STR)) {
- // add what we removed
- completeFuture(client.writeActionGroupMembers(groupProfileId, membersToRemove, INSERT, pipeconf),
- ACT_GRP_MEMS_STR, DELETE_STR);
- // recover group information
- completeFuture(client.writeActionGroup(groupOnDevice, MODIFY, pipeconf),
- ACT_GRP_STR, MODIFY_STR);
- // remove what we added
- completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, DELETE, pipeconf),
- ACT_GRP_MEMS_STR, INSERT_STR);
- return false;
- }
-
- return true;
- }
-
- private boolean writeGroupToDevice(PiActionGroup groupToApply) {
- // First insert members, then group.
- // The operation is deemed successful if both operations are successful.
- // FIXME: add transactional semantics, i.e. remove members if group fails.
- final boolean membersSuccess = completeFuture(
- client.writeActionGroupMembers(groupToApply.actionProfileId(),
- groupToApply.members(),
- INSERT, pipeconf),
- ACT_GRP_MEMS_STR, INSERT_STR);
- return membersSuccess && completeFuture(
- client.writeActionGroup(groupToApply, INSERT, pipeconf),
- ACT_GRP_STR, INSERT_STR);
- }
-
- private boolean deleteGroupFromDevice(PiActionGroup piActionGroup) {
- // First delete group, then members.
- // The operation is deemed successful if both operations are successful.
- final boolean groupSuccess = completeFuture(
- client.writeActionGroup(piActionGroup, DELETE, pipeconf),
- ACT_GRP_STR, DELETE_STR);
- return groupSuccess && completeFuture(
- client.writeActionGroupMembers(piActionGroup.actionProfileId(),
- piActionGroup.members(),
- DELETE, pipeconf),
- ACT_GRP_MEMS_STR, DELETE_STR);
- }
-
- private boolean completeFuture(CompletableFuture<Boolean> completableFuture,
- String topic, String action) {
- return getFutureWithDeadline(
- completableFuture, format("performing %s %s", action, topic), false);
- }
-
- private Stream<PiActionGroup> streamPiGroupsFromDevice(PiActionProfileId actProfId) {
- // Read PI groups and return original PD one.
- // TODO: implement P4Runtime client call to read all groups with one call
- // Good is pipeline has multiple action profiles.
- final Collection<PiActionGroup> groups = getFutureWithDeadline(
- client.dumpGroups(actProfId, pipeconf),
- "dumping groups", Collections.emptyList());
- return groups.stream();
- }
-
- private Group forgeGroupEntry(PiActionGroup piGroup) {
- final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup);
- final Optional<PiTranslatedEntity<Group, PiActionGroup>>
- translatedEntity = groupTranslator.lookup(handle);
- final TimedEntry<PiActionGroup> timedEntry = groupMirror.get(handle);
- // Is entry consistent with our state?
- if (!translatedEntity.isPresent()) {
- log.warn("Group handle not found in translation store: {}", handle);
- return null;
- }
- if (timedEntry == null) {
- // Don't bother logging more than debug, most probably it's the EC
- // map backing the store that has not received all the updates yet.
- log.debug("Group handle not found in device mirror: {}", handle);
- return null;
- }
- return addedGroup(translatedEntity.get().original(), timedEntry.lifeSec());
- }
-
- private Group forgeMcGroupEntry(PiMulticastGroupEntry mcGroup) {
- final PiMulticastGroupEntryHandle handle = PiMulticastGroupEntryHandle.of(
- deviceId, mcGroup);
- final Optional<PiTranslatedEntity<Group, PiMulticastGroupEntry>>
- translatedEntity = mcGroupTranslator.lookup(handle);
- final TimedEntry<PiMulticastGroupEntry> timedEntry = mcGroupMirror.get(handle);
- // Is entry consistent with our state?
- if (!translatedEntity.isPresent()) {
- log.warn("Multicast group handle not found in translation store: {}", handle);
- return null;
- }
- if (timedEntry == null) {
- log.warn("Multicast group handle not found in device mirror: {}", handle);
- return null;
- }
- return addedGroup(translatedEntity.get().original(), timedEntry.lifeSec());
- }
-
- private Group addedGroup(Group original, long life) {
- final DefaultGroup forgedGroup = new DefaultGroup(original.id(), original);
- forgedGroup.setState(Group.GroupState.ADDED);
- forgedGroup.setLife(life);
- return forgedGroup;
}
}
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMulticastGroupProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMulticastGroupProgrammable.java
new file mode 100644
index 0000000..a5c3538
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMulticastGroupProgrammable.java
@@ -0,0 +1,246 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Striped;
+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;
+import org.onosproject.net.group.GroupStore;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
+import org.onosproject.net.pi.runtime.PiMulticastGroupEntryHandle;
+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 java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.locks.Lock;
+import java.util.stream.Collectors;
+
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.MODIFY;
+
+/**
+ * Implementation of GroupProgrammable to handle multicast groups in P4Runtime.
+ */
+public class P4RuntimeMulticastGroupProgrammable
+ extends AbstractP4RuntimeHandlerBehaviour implements GroupProgrammable {
+
+ // Needed to synchronize operations over the same group.
+ private static final Striped<Lock> STRIPED_LOCKS = Striped.lock(30);
+
+ private GroupStore groupStore;
+ private P4RuntimeMulticastGroupMirror mcGroupMirror;
+ private PiMulticastGroupTranslator mcGroupTranslator;
+
+ @Override
+ protected boolean setupBehaviour() {
+ if (!super.setupBehaviour()) {
+ return false;
+ }
+ mcGroupMirror = this.handler().get(P4RuntimeMulticastGroupMirror.class);
+ groupStore = handler().get(GroupStore.class);
+ mcGroupTranslator = piTranslationService.multicastGroupTranslator();
+ return true;
+ }
+
+ @Override
+ public void performGroupOperation(DeviceId deviceId, GroupOperations groupOps) {
+ if (!setupBehaviour()) {
+ return;
+ }
+ groupOps.operations().stream()
+ .filter(op -> op.groupType().equals(GroupDescription.Type.ALL))
+ .forEach(op -> {
+ final Group group = groupStore.getGroup(deviceId, op.groupId());
+ processMcGroupOp(group, op.opType());
+ });
+ }
+
+ @Override
+ public Collection<Group> getGroups() {
+ if (!setupBehaviour()) {
+ return Collections.emptyList();
+ }
+ return ImmutableList.copyOf(getMcGroups());
+ }
+
+ private Collection<Group> getMcGroups() {
+ // TODO: missing support for reading multicast groups is ready in PI/Stratum.
+ return getMcGroupsFromMirror();
+ }
+
+ private Collection<Group> getMcGroupsFromMirror() {
+ return mcGroupMirror.getAll(deviceId).stream()
+ .map(TimedEntry::entry)
+ .map(this::forgeMcGroupEntry)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ private void processMcGroupOp(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();
+ }
+ }
+
+ private void processMcGroup(PiMulticastGroupEntryHandle handle,
+ PiMulticastGroupEntry groupToApply,
+ PiMulticastGroupEntry groupOnDevice,
+ Group pdGroup, GroupOperation.Type opType) {
+ switch (opType) {
+ case ADD:
+ robustMcGroupAdd(handle, groupToApply, pdGroup);
+ return;
+ case MODIFY:
+ // Since reading multicast groups is not supported yet on
+ // PI/Stratum, we cannot trust groupOnDevic) as we don't have a
+ // mechanism to enforce consistency of the mirror with the
+ // device state.
+ // if (driverBoolProperty(CHECK_MIRROR_BEFORE_UPDATE,
+ // DEFAULT_CHECK_MIRROR_BEFORE_UPDATE)
+ // && p4OpType == MODIFY
+ // && groupOnDevice != null
+ // && groupOnDevice.equals(groupToApply)) {
+ // // Ignore.
+ // return;
+ // }
+ robustMcGroupModify(handle, groupToApply, pdGroup);
+ return;
+ case DELETE:
+ mcGroupApply(handle, groupToApply, pdGroup, DELETE);
+ return;
+ default:
+ log.error("Unknown group operation type {}, " +
+ "cannot process multicast group", opType);
+ }
+ }
+
+ private boolean writeMcGroupOnDevice(PiMulticastGroupEntry group, P4RuntimeClient.WriteOperationType opType) {
+ return getFutureWithDeadline(
+ client.writePreMulticastGroupEntries(
+ Collections.singletonList(group), opType),
+ "performing multicast group " + opType, false);
+ }
+
+ private boolean mcGroupApply(PiMulticastGroupEntryHandle handle,
+ PiMulticastGroupEntry piGroup,
+ Group pdGroup,
+ P4RuntimeClient.WriteOperationType opType) {
+ switch (opType) {
+ case DELETE:
+ if (writeMcGroupOnDevice(piGroup, DELETE)) {
+ mcGroupMirror.remove(handle);
+ mcGroupTranslator.forget(handle);
+ return true;
+ } else {
+ return false;
+ }
+ case INSERT:
+ case MODIFY:
+ if (writeMcGroupOnDevice(piGroup, opType)) {
+ mcGroupMirror.put(handle, piGroup);
+ mcGroupTranslator.learn(handle, new PiTranslatedEntity<>(
+ pdGroup, piGroup, handle));
+ return true;
+ } else {
+ return false;
+ }
+ default:
+ log.warn("Unknown operation type {}, cannot apply group", opType);
+ return false;
+ }
+ }
+
+ private void robustMcGroupAdd(PiMulticastGroupEntryHandle handle,
+ PiMulticastGroupEntry piGroup,
+ Group pdGroup) {
+ if (mcGroupApply(handle, piGroup, pdGroup, INSERT)) {
+ return;
+ }
+ // Try to delete (perhaps it already exists) and re-add...
+ mcGroupApply(handle, piGroup, pdGroup, DELETE);
+ mcGroupApply(handle, piGroup, pdGroup, INSERT);
+ }
+
+ private void robustMcGroupModify(PiMulticastGroupEntryHandle handle,
+ PiMulticastGroupEntry piGroup,
+ Group pdGroup) {
+ if (mcGroupApply(handle, piGroup, pdGroup, MODIFY)) {
+ return;
+ }
+ // Not sure for which reason it cannot be modified, so try to delete and insert instead...
+ mcGroupApply(handle, piGroup, pdGroup, DELETE);
+ mcGroupApply(handle, piGroup, pdGroup, INSERT);
+ }
+
+ private Group forgeMcGroupEntry(PiMulticastGroupEntry mcGroup) {
+ final PiMulticastGroupEntryHandle handle = PiMulticastGroupEntryHandle.of(
+ deviceId, mcGroup);
+ final Optional<PiTranslatedEntity<Group, PiMulticastGroupEntry>>
+ translatedEntity = mcGroupTranslator.lookup(handle);
+ final TimedEntry<PiMulticastGroupEntry> timedEntry = mcGroupMirror.get(handle);
+ // Is entry consistent with our state?
+ if (!translatedEntity.isPresent()) {
+ log.warn("Multicast group handle not found in translation store: {}", handle);
+ return null;
+ }
+ if (timedEntry == null) {
+ log.warn("Multicast group handle not found in device mirror: {}", handle);
+ return null;
+ }
+ return addedGroup(translatedEntity.get().original(), timedEntry.lifeSec());
+ }
+
+ private Group addedGroup(Group original, long life) {
+ final DefaultGroup forgedGroup = new DefaultGroup(original.id(), original);
+ forgedGroup.setState(Group.GroupState.ADDED);
+ forgedGroup.setLife(life);
+ return forgedGroup;
+ }
+
+}
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/AbstractDistributedP4RuntimeMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/AbstractDistributedP4RuntimeMirror.java
index c5eb4c0..9230547 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/AbstractDistributedP4RuntimeMirror.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/AbstractDistributedP4RuntimeMirror.java
@@ -17,6 +17,7 @@
package org.onosproject.drivers.p4runtime.mirror;
import com.google.common.annotations.Beta;
+import com.google.common.collect.Maps;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
@@ -136,41 +137,54 @@
@Override
public void sync(DeviceId deviceId, Map<H, E> deviceState) {
checkNotNull(deviceId);
- Set<Map.Entry<H, TimedEntry<E>>> localEntries = getEntriesForDevice(deviceId);
+ final Map<H, E> localState = getMirrorMapForDevice(deviceId);
+
final AtomicInteger removeCount = new AtomicInteger(0);
final AtomicInteger updateCount = new AtomicInteger(0);
- localEntries.forEach(e -> {
- final H handle = e.getKey();
- final E storedValue = e.getValue().entry();
- if (!deviceState.containsKey(handle)) {
- log.debug("Removing mirror entry for {}: {}", deviceId, storedValue);
+ final AtomicInteger addCount = new AtomicInteger(0);
+ // Add missing entries.
+ deviceState.keySet().stream()
+ .filter(deviceHandle -> !localState.containsKey(deviceHandle))
+ .forEach(deviceHandle -> {
+ final E entryToAdd = deviceState.get(deviceHandle);
+ log.debug("Adding mirror entry for {}: {}",
+ deviceId, entryToAdd);
+ put(deviceHandle, entryToAdd);
+ addCount.incrementAndGet();
+ });
+ // Update or remove local entries.
+ localState.keySet().forEach(localHandle -> {
+ final E localEntry = localState.get(localHandle);
+ final E deviceEntry = deviceState.get(localHandle);
+ if (deviceEntry == null) {
+ log.debug("Removing mirror entry for {}: {}", deviceId, localEntry);
+ remove(localHandle);
removeCount.incrementAndGet();
- } else {
- final E deviceValue = deviceState.get(handle);
- if (!deviceValue.equals(storedValue)) {
- log.debug("Updating mirror entry for {}: {}-->{}",
- deviceId, storedValue, deviceValue);
- put(handle, deviceValue);
- updateCount.incrementAndGet();
- }
+ } else if (!deviceEntry.equals(localEntry)) {
+ log.debug("Updating mirror entry for {}: {}-->{}",
+ deviceId, localEntry, deviceEntry);
+ put(localHandle, deviceEntry);
+ updateCount.incrementAndGet();
}
});
- if (removeCount.get() + updateCount.get() > 0) {
- log.info("Synchronized mirror entries for {}: {} removed, {} updated",
- deviceId, removeCount, updateCount);
+ if (removeCount.get() + updateCount.get() + addCount.get() > 0) {
+ log.info("Synchronized mirror entries for {}: {} removed, {} updated, {} added",
+ deviceId, removeCount, updateCount, addCount);
}
}
- private Collection<H> getHandlesForDevice(DeviceId deviceId) {
+ private Set<H> getHandlesForDevice(DeviceId deviceId) {
return mirrorMap.keySet().stream()
.filter(h -> h.deviceId().equals(deviceId))
- .collect(Collectors.toList());
+ .collect(Collectors.toSet());
}
- private Set<Map.Entry<H, TimedEntry<E>>> getEntriesForDevice(DeviceId deviceId) {
- return mirrorMap.entrySet().stream()
+ private Map<H, E> getMirrorMapForDevice(DeviceId deviceId) {
+ final Map<H, E> deviceMap = Maps.newHashMap();
+ mirrorMap.entrySet().stream()
.filter(e -> e.getKey().deviceId().equals(deviceId))
- .collect(Collectors.toSet());
+ .forEach(e -> deviceMap.put(e.getKey(), e.getValue().entry()));
+ return deviceMap;
}
private void removeAll(DeviceId deviceId) {
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeActionProfileMemberMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeActionProfileMemberMirror.java
new file mode 100644
index 0000000..83edba4
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeActionProfileMemberMirror.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.PiActionGroupMember;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberHandle;
+import org.onosproject.store.serializers.KryoNamespaces;
+
+/**
+ * Distributed implementation of a P4Runtime action profile member mirror.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedP4RuntimeActionProfileMemberMirror
+ extends AbstractDistributedP4RuntimeMirror
+ <PiActionGroupMemberHandle, PiActionGroupMember>
+ implements P4RuntimeActionProfileMemberMirror {
+
+ private static final String DIST_MAP_NAME = "onos-p4runtime-act-prof-member-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/P4RuntimeActionProfileMemberMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeActionProfileMemberMirror.java
new file mode 100644
index 0000000..8ab1fa0
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeActionProfileMemberMirror.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.PiActionGroupMember;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberHandle;
+
+/**
+ * Mirror of action profile members installed on a P4Runtime device.
+ */
+public interface P4RuntimeActionProfileMemberMirror
+ extends P4RuntimeMirror<PiActionGroupMemberHandle, PiActionGroupMember> {
+}
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 562f0ae..8c1d7d9 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
@@ -24,6 +24,7 @@
import org.onosproject.net.pi.model.PiTableId;
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.PiCounterCellData;
import org.onosproject.net.pi.runtime.PiCounterCellId;
import org.onosproject.net.pi.runtime.PiMeterCellConfig;
@@ -33,7 +34,7 @@
import org.onosproject.net.pi.runtime.PiTableEntry;
import java.nio.ByteBuffer;
-import java.util.Collection;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -132,7 +133,7 @@
* @return true if the operation was successful, false otherwise.
*/
CompletableFuture<Boolean> writeTableEntries(
- Collection<PiTableEntry> entries, WriteOperationType opType,
+ List<PiTableEntry> entries, WriteOperationType opType,
PiPipeconf pipeconf);
/**
@@ -141,21 +142,22 @@
* entries will be returned, otherwise non-default entries will be
* considered.
*
- * @param tableIds table identifiers
- * @param defaultEntries true to read default entries, false for non-default
- * @param pipeconf pipeconf currently deployed on the device
- * @return completable future of a collection of table entries
+ * @param tableIds table identifiers
+ * @param defaultEntries true to read default entries, false for
+ * non-default
+ * @param pipeconf pipeconf currently deployed on the device
+ * @return completable future of a list of table entries
*/
- CompletableFuture<Collection<PiTableEntry>> dumpTables(
+ CompletableFuture<List<PiTableEntry>> dumpTables(
Set<PiTableId> tableIds, boolean defaultEntries, PiPipeconf pipeconf);
/**
* Dumps entries from all tables, for the given pipeconf.
*
* @param pipeconf pipeconf currently deployed on the device
- * @return completable future of a collection of table entries
+ * @return completable future of a list of table entries
*/
- CompletableFuture<Collection<PiTableEntry>> dumpAllTables(PiPipeconf pipeconf);
+ CompletableFuture<List<PiTableEntry>> dumpAllTables(PiPipeconf pipeconf);
/**
* Executes a packet-out operation for the given pipeconf.
@@ -174,34 +176,33 @@
*
* @param counterIds counter identifiers
* @param pipeconf pipeconf
- * @return collection of counter data
+ * @return list of counter data
*/
- CompletableFuture<Collection<PiCounterCellData>> readAllCounterCells(
+ CompletableFuture<List<PiCounterCellData>> readAllCounterCells(
Set<PiCounterId> counterIds, PiPipeconf pipeconf);
/**
- * Returns a collection of counter data corresponding to the given set of
- * counter cell identifiers, for the given pipeconf.
+ * Returns a list of counter data corresponding to the given set of counter
+ * cell identifiers, for the given pipeconf.
*
* @param cellIds set of counter cell identifiers
* @param pipeconf pipeconf
- * @return collection of counter data
+ * @return list of counter data
*/
- CompletableFuture<Collection<PiCounterCellData>> readCounterCells(
+ CompletableFuture<List<PiCounterCellData>> readCounterCells(
Set<PiCounterCellId> cellIds, PiPipeconf pipeconf);
/**
* Performs the given write operation for the given action group members and
* pipeconf.
*
- * @param profileId action group profile ID
- * @param members action group members
- * @param opType write operation type
- * @param pipeconf the pipeconf currently deployed on the device
+ * @param members action group members
+ * @param opType write operation type
+ * @param pipeconf the pipeconf currently deployed on the device
* @return true if the operation was successful, false otherwise
*/
CompletableFuture<Boolean> writeActionGroupMembers(
- PiActionProfileId profileId, Collection<PiActionGroupMember> members,
+ List<PiActionGroupMember> members,
WriteOperationType opType, PiPipeconf pipeconf);
/**
@@ -221,31 +222,55 @@
*
* @param actionProfileId the action profile id
* @param pipeconf the pipeconf currently deployed on the device
- * @return completable future of a collection of groups
+ * @return completable future of a list of groups
*/
- CompletableFuture<Collection<PiActionGroup>> dumpGroups(
+ CompletableFuture<List<PiActionGroup>> dumpGroups(
PiActionProfileId actionProfileId, PiPipeconf pipeconf);
/**
+ * Dumps all action profile member IDs for a given action profile.
+ *
+ * @param actionProfileId action profile ID
+ * @param pipeconf pipeconf
+ * @return future of list of action profile member ID
+ */
+ CompletableFuture<List<PiActionGroupMemberId>> dumpActionProfileMemberIds(
+ PiActionProfileId actionProfileId, PiPipeconf pipeconf);
+
+ /**
+ * Removes the given members from the given action profile. Returns the list
+ * of successfully removed members.
+ *
+ * @param actionProfileId action profile ID
+ * @param memberIds member IDs
+ * @param pipeconf pipeconf
+ * @return list of member IDs that were successfully removed from the device
+ */
+ CompletableFuture<List<PiActionGroupMemberId>> removeActionProfileMembers(
+ PiActionProfileId actionProfileId,
+ List<PiActionGroupMemberId> memberIds,
+ PiPipeconf pipeconf);
+
+ /**
* Returns the configuration of all meter cells for the given set of meter
* identifiers and pipeconf.
*
* @param meterIds meter identifiers
* @param pipeconf pipeconf
- * @return collection of meter configurations
+ * @return list of meter configurations
*/
- CompletableFuture<Collection<PiMeterCellConfig>> readAllMeterCells(
+ CompletableFuture<List<PiMeterCellConfig>> readAllMeterCells(
Set<PiMeterId> meterIds, PiPipeconf pipeconf);
/**
- * Returns a collection of meter configurations corresponding to the given
- * set of meter cell identifiers, for the given pipeconf.
+ * Returns a list of meter configurations corresponding to the given set of
+ * meter cell identifiers, for the given pipeconf.
*
* @param cellIds set of meter cell identifiers
* @param pipeconf pipeconf
- * @return collection of meter configrations
+ * @return list of meter configrations
*/
- CompletableFuture<Collection<PiMeterCellConfig>> readMeterCells(
+ CompletableFuture<List<PiMeterCellConfig>> readMeterCells(
Set<PiMeterCellId> cellIds, PiPipeconf pipeconf);
/**
@@ -257,7 +282,7 @@
* @return true if the operation was successful, false otherwise.
*/
CompletableFuture<Boolean> writeMeterCells(
- Collection<PiMeterCellConfig> cellConfigs, PiPipeconf pipeconf);
+ List<PiMeterCellConfig> cellConfigs, PiPipeconf pipeconf);
/**
* Performs the given write operation for the given PI multicast groups
@@ -268,7 +293,7 @@
* @return true if the operation was successful, false otherwise
*/
CompletableFuture<Boolean> writePreMulticastGroupEntries(
- Collection<PiMulticastGroupEntry> entries,
+ List<PiMulticastGroupEntry> entries,
WriteOperationType opType);
/**
@@ -276,5 +301,5 @@
*
* @return multicast groups
*/
- CompletableFuture<Collection<PiMulticastGroupEntry>> readAllMulticastGroupEntries();
+ CompletableFuture<List<PiMulticastGroupEntry>> readAllMulticastGroupEntries();
}
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupEncoder.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupEncoder.java
index 20f39df..709b92e 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupEncoder.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupEncoder.java
@@ -21,10 +21,10 @@
import org.onosproject.net.pi.model.PiPipeconf;
import org.onosproject.net.pi.runtime.PiActionGroup;
import org.onosproject.net.pi.runtime.PiActionGroupId;
+import p4.config.v1.P4InfoOuterClass;
import p4.v1.P4RuntimeOuterClass.ActionProfileGroup;
import p4.v1.P4RuntimeOuterClass.ActionProfileGroup.Member;
import p4.v1.P4RuntimeOuterClass.ActionProfileMember;
-import p4.config.v1.P4InfoOuterClass;
import java.util.Collection;
import java.util.Map;
@@ -36,6 +36,8 @@
*/
final class ActionProfileGroupEncoder {
+ private static final int GROUP_SIZE_ADDITIONAL_MEMBERS = 10;
+
private ActionProfileGroupEncoder() {
// hide default constructor
}
@@ -44,10 +46,12 @@
* Encode a PI action group to a action profile group.
*
* @param piActionGroup the action profile group
- * @param pipeconf the pipeconf
+ * @param pipeconf the pipeconf
* @return a action profile group encoded from PI action group
- * @throws P4InfoBrowser.NotFoundException if can't find action profile from P4Info browser
- * @throws EncodeException if can't find P4Info from pipeconf
+ * @throws P4InfoBrowser.NotFoundException if can't find action profile from
+ * P4Info browser
+ * @throws EncodeException if can't find P4Info from
+ * pipeconf
*/
static ActionProfileGroup encode(PiActionGroup piActionGroup, PiPipeconf pipeconf)
throws P4InfoBrowser.NotFoundException, EncodeException {
@@ -62,8 +66,8 @@
.getByName(piActionProfileId.id());
int actionProfileId = actionProfile.getPreamble().getId();
ActionProfileGroup.Builder actionProfileGroupBuilder = ActionProfileGroup.newBuilder()
- .setGroupId(piActionGroup.id().id())
- .setActionProfileId(actionProfileId);
+ .setGroupId(piActionGroup.id().id())
+ .setActionProfileId(actionProfileId);
piActionGroup.members().forEach(m -> {
// TODO: currently we don't set "watch" field of member
@@ -74,20 +78,31 @@
actionProfileGroupBuilder.addMembers(member);
});
- actionProfileGroupBuilder.setMaxSize(piActionGroup.members().size());
+ // FIXME: ONOS-7797 Make this configurable, or find a different way of
+ // supporting group modify. In P4Runtime, group size cannot be modified
+ // once the group is created. To allow adding members to an existing
+ // group we set max_size to support an additional number of members
+ // other than the one already defined in the PI group. Clearly, this
+ // will break if we try to add more than GROUP_SIZE_ADDITIONAL_MEMBERS
+ // to the same group.
+ actionProfileGroupBuilder.setMaxSize(
+ piActionGroup.members().size() + GROUP_SIZE_ADDITIONAL_MEMBERS);
return actionProfileGroupBuilder.build();
}
/**
- * Decode an action profile group with members information to a PI action group.
+ * Decode an action profile group with members information to a PI action
+ * group.
*
* @param actionProfileGroup the action profile group
- * @param members members of the action profile group
- * @param pipeconf the pipeconf
+ * @param members members of the action profile group
+ * @param pipeconf the pipeconf
* @return decoded PI action group
- * @throws P4InfoBrowser.NotFoundException if can't find action profile from P4Info browser
- * @throws EncodeException if can't find P4Info from pipeconf
+ * @throws P4InfoBrowser.NotFoundException if can't find action profile from
+ * P4Info browser
+ * @throws EncodeException if can't find P4Info from
+ * pipeconf
*/
static PiActionGroup decode(ActionProfileGroup actionProfileGroup,
Collection<ActionProfileMember> members,
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileMemberEncoder.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileMemberEncoder.java
index 4dbcac3..e78aa97 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileMemberEncoder.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileMemberEncoder.java
@@ -39,16 +39,14 @@
/**
* Encode a PiActionGroupMember to a ActionProfileMember.
*
- * @param profileId the PI action group profile ID of members
- * @param member the member to encode
- * @param pipeconf the pipeconf, as encode spec
+ * @param member the member to encode
+ * @param pipeconf the pipeconf, as encode spec
* @return encoded member
* @throws P4InfoBrowser.NotFoundException can't find action profile from
* P4Info browser
* @throws EncodeException can't find P4Info from pipeconf
*/
- static ActionProfileMember encode(PiActionProfileId profileId,
- PiActionGroupMember member,
+ static ActionProfileMember encode(PiActionGroupMember member,
PiPipeconf pipeconf)
throws P4InfoBrowser.NotFoundException, EncodeException {
@@ -66,7 +64,7 @@
// action profile id
P4InfoOuterClass.ActionProfile actionProfile =
- browser.actionProfiles().getByName(profileId.id());
+ browser.actionProfiles().getByName(member.actionProfile().id());
int actionProfileId = actionProfile.getPreamble().getId();
actionProfileMemberBuilder.setActionProfileId(actionProfileId);
@@ -95,11 +93,19 @@
PiPipeconf pipeconf)
throws P4InfoBrowser.NotFoundException, EncodeException {
P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
-
if (browser == null) {
throw new EncodeException(format("Can't get P4 info browser from pipeconf %s", pipeconf));
}
- return PiActionGroupMember.builder().withId(PiActionGroupMemberId.of(member.getMemberId()))
+
+ final PiActionProfileId actionProfileId = PiActionProfileId.of(
+ browser.actionProfiles()
+ .getById(member.getActionProfileId())
+ .getPreamble()
+ .getName());
+
+ return PiActionGroupMember.builder()
+ .forActionProfile(actionProfileId)
+ .withId(PiActionGroupMemberId.of(member.getMemberId()))
.withWeight(weight)
.withAction(decodeActionMsg(member.getAction(), browser))
.build();
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java
index ec50c68..e883821 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java
@@ -30,8 +30,8 @@
import p4.v1.P4RuntimeOuterClass.DirectCounterEntry;
import p4.v1.P4RuntimeOuterClass.Entity;
-import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -65,7 +65,7 @@
* @return collection of entity messages describing both counter or direct
* counter entries
*/
- static Collection<Entity> encodePiCounterCellIds(Collection<PiCounterCellId> cellIds,
+ static List<Entity> encodePiCounterCellIds(List<PiCounterCellId> cellIds,
PiPipeconf pipeconf) {
final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
@@ -100,7 +100,7 @@
* @param pipeconf pipeconf
* @return collection of entity messages
*/
- static Collection<Entity> readAllCellsEntities(Collection<PiCounterId> counterIds,
+ static List<Entity> readAllCellsEntities(List<PiCounterId> counterIds,
PiPipeconf pipeconf) {
final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
@@ -135,8 +135,8 @@
* @param pipeconf pipeconf
* @return collection of PI counter cell data
*/
- static Collection<PiCounterCellData> decodeCounterEntities(Collection<Entity> entities,
- PiPipeconf pipeconf) {
+ static List<PiCounterCellData> decodeCounterEntities(List<Entity> entities,
+ PiPipeconf pipeconf) {
final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MeterEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MeterEntryCodec.java
index 12379fd..a825488 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MeterEntryCodec.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MeterEntryCodec.java
@@ -31,8 +31,8 @@
import p4.v1.P4RuntimeOuterClass.MeterConfig;
import p4.v1.P4RuntimeOuterClass.MeterEntry;
-import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -66,7 +66,7 @@
* @return collection of entity messages describing both meter or direct
* meter entries
*/
- static Collection<Entity> encodePiMeterCellConfigs(Collection<PiMeterCellConfig> cellConfigs,
+ static List<Entity> encodePiMeterCellConfigs(List<PiMeterCellConfig> cellConfigs,
PiPipeconf pipeconf) {
final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
@@ -101,7 +101,7 @@
* @param pipeconf pipeconf
* @return collection of entity messages
*/
- static Collection<Entity> readAllCellsEntities(Collection<PiMeterId> meterIds,
+ static List<Entity> readAllCellsEntities(List<PiMeterId> meterIds,
PiPipeconf pipeconf) {
final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
@@ -136,8 +136,8 @@
* @param pipeconf pipeconf
* @return collection of PI meter cell data
*/
- static Collection<PiMeterCellConfig> decodeMeterEntities(Collection<Entity> entities,
- PiPipeconf pipeconf) {
+ static List<PiMeterCellConfig> decodeMeterEntities(List<Entity> entities,
+ PiPipeconf pipeconf) {
final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
if (browser == null) {
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 e020ed5..54d284d 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
@@ -31,7 +31,6 @@
import io.grpc.protobuf.lite.ProtoLiteUtils;
import io.grpc.stub.ClientCallStreamObserver;
import io.grpc.stub.StreamObserver;
-import org.apache.commons.lang3.tuple.ImmutablePair;
import org.onlab.osgi.DefaultServiceDirectory;
import org.onlab.util.SharedExecutors;
import org.onlab.util.Tools;
@@ -43,9 +42,9 @@
import org.onosproject.net.pi.model.PiTableId;
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.PiCounterCellData;
import org.onosproject.net.pi.runtime.PiCounterCellId;
-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;
@@ -81,7 +80,6 @@
import java.math.BigInteger;
import java.net.ConnectException;
import java.nio.ByteBuffer;
-import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -102,6 +100,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
+import static java.util.Collections.singletonList;
import static org.onlab.util.Tools.groupedThreads;
import static org.slf4j.LoggerFactory.getLogger;
import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.ACTION_PROFILE_GROUP;
@@ -258,21 +257,21 @@
}
@Override
- public CompletableFuture<Boolean> writeTableEntries(Collection<PiTableEntry> piTableEntries,
+ public CompletableFuture<Boolean> writeTableEntries(List<PiTableEntry> piTableEntries,
WriteOperationType opType, PiPipeconf pipeconf) {
return supplyInContext(() -> doWriteTableEntries(piTableEntries, opType, pipeconf),
"writeTableEntries-" + opType.name());
}
@Override
- public CompletableFuture<Collection<PiTableEntry>> dumpTables(
+ public CompletableFuture<List<PiTableEntry>> dumpTables(
Set<PiTableId> piTableIds, boolean defaultEntries, PiPipeconf pipeconf) {
return supplyInContext(() -> doDumpTables(piTableIds, defaultEntries, pipeconf),
"dumpTables-" + piTableIds.hashCode());
}
@Override
- public CompletableFuture<Collection<PiTableEntry>> dumpAllTables(PiPipeconf pipeconf) {
+ public CompletableFuture<List<PiTableEntry>> dumpAllTables(PiPipeconf pipeconf) {
return supplyInContext(() -> doDumpTables(null, false, pipeconf), "dumpAllTables");
}
@@ -282,25 +281,24 @@
}
@Override
- public CompletableFuture<Collection<PiCounterCellData>> readCounterCells(Set<PiCounterCellId> cellIds,
- PiPipeconf pipeconf) {
- return supplyInContext(() -> doReadCounterCells(cellIds, pipeconf),
+ public CompletableFuture<List<PiCounterCellData>> readCounterCells(Set<PiCounterCellId> cellIds,
+ PiPipeconf pipeconf) {
+ return supplyInContext(() -> doReadCounterCells(Lists.newArrayList(cellIds), pipeconf),
"readCounterCells-" + cellIds.hashCode());
}
@Override
- public CompletableFuture<Collection<PiCounterCellData>> readAllCounterCells(Set<PiCounterId> counterIds,
- PiPipeconf pipeconf) {
- return supplyInContext(() -> doReadAllCounterCells(counterIds, pipeconf),
+ public CompletableFuture<List<PiCounterCellData>> readAllCounterCells(Set<PiCounterId> counterIds,
+ PiPipeconf pipeconf) {
+ return supplyInContext(() -> doReadAllCounterCells(Lists.newArrayList(counterIds), pipeconf),
"readAllCounterCells-" + counterIds.hashCode());
}
@Override
- public CompletableFuture<Boolean> writeActionGroupMembers(PiActionProfileId profileId,
- Collection<PiActionGroupMember> members,
+ public CompletableFuture<Boolean> writeActionGroupMembers(List<PiActionGroupMember> members,
WriteOperationType opType,
PiPipeconf pipeconf) {
- return supplyInContext(() -> doWriteActionGroupMembers(profileId, members, opType, pipeconf),
+ return supplyInContext(() -> doWriteActionGroupMembers(members, opType, pipeconf),
"writeActionGroupMembers-" + opType.name());
}
@@ -314,14 +312,31 @@
}
@Override
- public CompletableFuture<Collection<PiActionGroup>> dumpGroups(PiActionProfileId actionProfileId,
- PiPipeconf pipeconf) {
+ public CompletableFuture<List<PiActionGroup>> dumpGroups(PiActionProfileId actionProfileId,
+ PiPipeconf pipeconf) {
return supplyInContext(() -> doDumpGroups(actionProfileId, pipeconf),
"dumpGroups-" + actionProfileId.id());
}
@Override
- public CompletableFuture<Boolean> writeMeterCells(Collection<PiMeterCellConfig> cellIds, PiPipeconf pipeconf) {
+ public CompletableFuture<List<PiActionGroupMemberId>> dumpActionProfileMemberIds(
+ PiActionProfileId actionProfileId, PiPipeconf pipeconf) {
+ return supplyInContext(() -> doDumpActionProfileMemberIds(actionProfileId, pipeconf),
+ "dumpActionProfileMemberIds-" + actionProfileId.id());
+ }
+
+ @Override
+ public CompletableFuture<List<PiActionGroupMemberId>> removeActionProfileMembers(
+ PiActionProfileId actionProfileId,
+ List<PiActionGroupMemberId> memberIds,
+ PiPipeconf pipeconf) {
+ return supplyInContext(
+ () -> doRemoveActionProfileMembers(actionProfileId, memberIds, pipeconf),
+ "cleanupActionProfileMembers-" + actionProfileId.id());
+ }
+
+ @Override
+ public CompletableFuture<Boolean> writeMeterCells(List<PiMeterCellConfig> cellIds, PiPipeconf pipeconf) {
return supplyInContext(() -> doWriteMeterCells(cellIds, pipeconf),
"writeMeterCells");
@@ -329,29 +344,29 @@
@Override
public CompletableFuture<Boolean> writePreMulticastGroupEntries(
- Collection<PiMulticastGroupEntry> entries,
+ List<PiMulticastGroupEntry> entries,
WriteOperationType opType) {
return supplyInContext(() -> doWriteMulticastGroupEntries(entries, opType),
"writePreMulticastGroupEntries");
}
@Override
- public CompletableFuture<Collection<PiMulticastGroupEntry>> readAllMulticastGroupEntries() {
+ public CompletableFuture<List<PiMulticastGroupEntry>> readAllMulticastGroupEntries() {
return supplyInContext(this::doReadAllMulticastGroupEntries,
"readAllMulticastGroupEntries");
}
@Override
- public CompletableFuture<Collection<PiMeterCellConfig>> readMeterCells(Set<PiMeterCellId> cellIds,
- PiPipeconf pipeconf) {
- return supplyInContext(() -> doReadMeterCells(cellIds, pipeconf),
+ public CompletableFuture<List<PiMeterCellConfig>> readMeterCells(Set<PiMeterCellId> cellIds,
+ PiPipeconf pipeconf) {
+ return supplyInContext(() -> doReadMeterCells(Lists.newArrayList(cellIds), pipeconf),
"readMeterCells-" + cellIds.hashCode());
}
@Override
- public CompletableFuture<Collection<PiMeterCellConfig>> readAllMeterCells(Set<PiMeterId> meterIds,
- PiPipeconf pipeconf) {
- return supplyInContext(() -> doReadAllMeterCells(meterIds, pipeconf),
+ public CompletableFuture<List<PiMeterCellConfig>> readAllMeterCells(Set<PiMeterId> meterIds,
+ PiPipeconf pipeconf) {
+ return supplyInContext(() -> doReadAllMeterCells(Lists.newArrayList(meterIds), pipeconf),
"readAllMeterCells-" + meterIds.hashCode());
}
@@ -491,13 +506,13 @@
}
}
- private boolean doWriteTableEntries(Collection<PiTableEntry> piTableEntries, WriteOperationType opType,
+ private boolean doWriteTableEntries(List<PiTableEntry> piTableEntries, WriteOperationType opType,
PiPipeconf pipeconf) {
if (piTableEntries.size() == 0) {
return true;
}
- Collection<Update> updateMsgs;
+ List<Update> updateMsgs;
try {
updateMsgs = TableEntryEncoder.encode(piTableEntries, pipeconf)
.stream()
@@ -518,7 +533,7 @@
return write(updateMsgs, piTableEntries, opType, "table entry");
}
- private Collection<PiTableEntry> doDumpTables(
+ private List<PiTableEntry> doDumpTables(
Set<PiTableId> piTableIds, boolean defaultEntries, PiPipeconf pipeconf) {
log.debug("Dumping tables {} from {} (pipeconf {})...",
@@ -647,22 +662,22 @@
isClientMaster.set(isMaster);
}
- private Collection<PiCounterCellData> doReadAllCounterCells(
- Collection<PiCounterId> counterIds, PiPipeconf pipeconf) {
+ private List<PiCounterCellData> doReadAllCounterCells(
+ List<PiCounterId> counterIds, PiPipeconf pipeconf) {
return doReadCounterEntities(
CounterEntryCodec.readAllCellsEntities(counterIds, pipeconf),
pipeconf);
}
- private Collection<PiCounterCellData> doReadCounterCells(
- Collection<PiCounterCellId> cellIds, PiPipeconf pipeconf) {
+ private List<PiCounterCellData> doReadCounterCells(
+ List<PiCounterCellId> cellIds, PiPipeconf pipeconf) {
return doReadCounterEntities(
CounterEntryCodec.encodePiCounterCellIds(cellIds, pipeconf),
pipeconf);
}
- private Collection<PiCounterCellData> doReadCounterEntities(
- Collection<Entity> counterEntities, PiPipeconf pipeconf) {
+ private List<PiCounterCellData> doReadCounterEntities(
+ List<Entity> counterEntities, PiPipeconf pipeconf) {
if (counterEntities.size() == 0) {
return Collections.emptyList();
@@ -690,13 +705,13 @@
return CounterEntryCodec.decodeCounterEntities(entities, pipeconf);
}
- private boolean doWriteActionGroupMembers(PiActionProfileId profileId, Collection<PiActionGroupMember> members,
+ private boolean doWriteActionGroupMembers(List<PiActionGroupMember> members,
WriteOperationType opType, PiPipeconf pipeconf) {
- final Collection<ActionProfileMember> actionProfileMembers = Lists.newArrayList();
+ final List<ActionProfileMember> actionProfileMembers = Lists.newArrayList();
for (PiActionGroupMember member : members) {
try {
- actionProfileMembers.add(ActionProfileMemberEncoder.encode(profileId, member, pipeconf));
+ actionProfileMembers.add(ActionProfileMemberEncoder.encode(member, pipeconf));
} catch (EncodeException | P4InfoBrowser.NotFoundException e) {
log.warn("Unable to encode group member, aborting {} operation: {} [{}]",
opType.name(), e.getMessage(), member.toString());
@@ -704,7 +719,7 @@
}
}
- final Collection<Update> updateMsgs = actionProfileMembers.stream()
+ final List<Update> updateMsgs = actionProfileMembers.stream()
.map(actionProfileMember ->
Update.newBuilder()
.setEntity(Entity.newBuilder()
@@ -722,14 +737,14 @@
return write(updateMsgs, members, opType, "group member");
}
- private Collection<PiActionGroup> doDumpGroups(PiActionProfileId piActionProfileId, PiPipeconf pipeconf) {
+ private List<PiActionGroup> doDumpGroups(PiActionProfileId piActionProfileId, PiPipeconf pipeconf) {
log.debug("Dumping groups from action profile {} from {} (pipeconf {})...",
piActionProfileId.id(), deviceId, pipeconf.id());
final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
if (browser == null) {
log.warn("Unable to get a P4Info browser for pipeconf {}, aborting dump action profile", pipeconf);
- return Collections.emptySet();
+ return Collections.emptyList();
}
final int actionProfileId;
@@ -741,7 +756,7 @@
.getId();
} catch (P4InfoBrowser.NotFoundException e) {
log.warn("Unable to dump groups: {}", e.getMessage());
- return Collections.emptySet();
+ return Collections.emptyList();
}
// Prepare read request to read all groups from the given action profile.
@@ -762,7 +777,7 @@
} catch (StatusRuntimeException e) {
checkGrpcException(e);
log.warn("Unable to dump action profile {} from {}: {}", piActionProfileId, deviceId, e.getMessage());
- return Collections.emptySet();
+ return Collections.emptyList();
}
final List<ActionProfileGroup> groupMsgs = Tools.stream(() -> groupResponses)
@@ -847,6 +862,104 @@
.collect(Collectors.toList());
}
+ private List<PiActionGroupMemberId> doDumpActionProfileMemberIds(
+ PiActionProfileId actionProfileId, PiPipeconf pipeconf) {
+
+ final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+ if (browser == null) {
+ log.warn("Unable to get a P4Info browser for pipeconf {}, " +
+ "aborting cleanup of action profile members",
+ pipeconf);
+ return Collections.emptyList();
+ }
+
+ final int p4ActProfId;
+ try {
+ p4ActProfId = browser
+ .actionProfiles()
+ .getByName(actionProfileId.id())
+ .getPreamble()
+ .getId();
+ } catch (P4InfoBrowser.NotFoundException e) {
+ log.warn("Unable to cleanup action profile members: {}", e.getMessage());
+ return Collections.emptyList();
+ }
+
+ final ReadRequest memberRequestMsg = ReadRequest.newBuilder()
+ .setDeviceId(p4DeviceId)
+ .addEntities(Entity.newBuilder().setActionProfileMember(
+ ActionProfileMember.newBuilder()
+ .setActionProfileId(p4ActProfId)
+ .build()).build())
+ .build();
+
+ // Read members.
+ final Iterator<ReadResponse> memberResponses;
+ try {
+ memberResponses = blockingStub.read(memberRequestMsg);
+ } catch (StatusRuntimeException e) {
+ checkGrpcException(e);
+ log.warn("Unable to read members of action profile {} from {}: {}",
+ actionProfileId, deviceId, e.getMessage());
+ return Collections.emptyList();
+ }
+
+ return Tools.stream(() -> memberResponses)
+ .map(ReadResponse::getEntitiesList)
+ .flatMap(List::stream)
+ .filter(e -> e.getEntityCase() == ACTION_PROFILE_MEMBER)
+ .map(Entity::getActionProfileMember)
+ // Perhaps not needed, but better to double check to avoid
+ // removing members of other groups.
+ .filter(m -> m.getActionProfileId() == p4ActProfId)
+ .map(ActionProfileMember::getMemberId)
+ .map(PiActionGroupMemberId::of)
+ .collect(Collectors.toList());
+ }
+
+ private List<PiActionGroupMemberId> doRemoveActionProfileMembers(
+ PiActionProfileId actionProfileId,
+ List<PiActionGroupMemberId> memberIds,
+ PiPipeconf pipeconf) {
+
+ if (memberIds.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+ if (browser == null) {
+ log.warn("Unable to get a P4Info browser for pipeconf {}, " +
+ "aborting cleanup of action profile members",
+ pipeconf);
+ return Collections.emptyList();
+ }
+
+ final int p4ActProfId;
+ try {
+ p4ActProfId = browser.actionProfiles()
+ .getByName(actionProfileId.id()).getPreamble().getId();
+ } catch (P4InfoBrowser.NotFoundException e) {
+ log.warn("Unable to cleanup action profile members: {}", e.getMessage());
+ return Collections.emptyList();
+ }
+
+ final List<Update> updateMsgs = memberIds.stream()
+ .map(m -> ActionProfileMember.newBuilder()
+ .setActionProfileId(p4ActProfId)
+ .setMemberId(m.id()).build())
+ .map(m -> Entity.newBuilder().setActionProfileMember(m).build())
+ .map(e -> Update.newBuilder().setEntity(e)
+ .setType(Update.Type.DELETE).build())
+ .collect(Collectors.toList());
+
+ log.debug("Removing {} members of action profile '{}'...",
+ memberIds.size(), actionProfileId);
+
+ return writeAndReturnSuccessEntities(
+ updateMsgs, memberIds, WriteOperationType.DELETE,
+ "action profile members");
+ }
+
private boolean doWriteActionGroup(PiActionGroup group, WriteOperationType opType, PiPipeconf pipeconf) {
final ActionProfileGroup actionProfileGroup;
try {
@@ -863,20 +976,20 @@
.setType(UPDATE_TYPES.get(opType))
.build();
- return write(Collections.singleton(updateMsg), Collections.singleton(group),
+ return write(singletonList(updateMsg), singletonList(group),
opType, "group");
}
- private Collection<PiMeterCellConfig> doReadAllMeterCells(
- Collection<PiMeterId> meterIds, PiPipeconf pipeconf) {
+ private List<PiMeterCellConfig> doReadAllMeterCells(
+ List<PiMeterId> meterIds, PiPipeconf pipeconf) {
return doReadMeterEntities(MeterEntryCodec.readAllCellsEntities(
meterIds, pipeconf), pipeconf);
}
- private Collection<PiMeterCellConfig> doReadMeterCells(
- Collection<PiMeterCellId> cellIds, PiPipeconf pipeconf) {
+ private List<PiMeterCellConfig> doReadMeterCells(
+ List<PiMeterCellId> cellIds, PiPipeconf pipeconf) {
- final Collection<PiMeterCellConfig> piMeterCellConfigs = cellIds.stream()
+ final List<PiMeterCellConfig> piMeterCellConfigs = cellIds.stream()
.map(cellId -> PiMeterCellConfig.builder()
.withMeterCellId(cellId)
.build())
@@ -886,8 +999,8 @@
piMeterCellConfigs, pipeconf), pipeconf);
}
- private Collection<PiMeterCellConfig> doReadMeterEntities(
- Collection<Entity> entitiesToRead, PiPipeconf pipeconf) {
+ private List<PiMeterCellConfig> doReadMeterEntities(
+ List<Entity> entitiesToRead, PiPipeconf pipeconf) {
if (entitiesToRead.size() == 0) {
return Collections.emptyList();
@@ -917,9 +1030,9 @@
return MeterEntryCodec.decodeMeterEntities(responseEntities, pipeconf);
}
- private boolean doWriteMeterCells(Collection<PiMeterCellConfig> cellConfigs, PiPipeconf pipeconf) {
+ private boolean doWriteMeterCells(List<PiMeterCellConfig> cellConfigs, PiPipeconf pipeconf) {
- Collection<Update> updateMsgs = MeterEntryCodec.encodePiMeterCellConfigs(cellConfigs, pipeconf)
+ List<Update> updateMsgs = MeterEntryCodec.encodePiMeterCellConfigs(cellConfigs, pipeconf)
.stream()
.map(meterEntryMsg ->
Update.newBuilder()
@@ -936,7 +1049,7 @@
}
private boolean doWriteMulticastGroupEntries(
- Collection<PiMulticastGroupEntry> entries,
+ List<PiMulticastGroupEntry> entries,
WriteOperationType opType) {
final List<Update> updateMsgs = entries.stream()
@@ -955,7 +1068,7 @@
return write(updateMsgs, entries, opType, "multicast group entry");
}
- private Collection<PiMulticastGroupEntry> doReadAllMulticastGroupEntries() {
+ private List<PiMulticastGroupEntry> doReadAllMulticastGroupEntries() {
final Entity entity = Entity.newBuilder()
.setPacketReplicationEngineEntry(
@@ -999,18 +1112,24 @@
return mcEntries;
}
- private <E extends PiEntity> boolean write(Collection<Update> updates,
- Collection<E> writeEntities,
- WriteOperationType opType,
- String entryType) {
- try {
+ private <T> boolean write(List<Update> updates,
+ List<T> writeEntities,
+ WriteOperationType opType,
+ String entryType) {
+ // True if all entities were successfully written.
+ return writeAndReturnSuccessEntities(updates, writeEntities, opType,
+ entryType).size() == writeEntities.size();
+ }
+ private <T> List<T> writeAndReturnSuccessEntities(
+ List<Update> updates, List<T> writeEntities,
+ WriteOperationType opType, String entryType) {
+ try {
//noinspection ResultOfMethodCallIgnored
blockingStub.write(writeRequest(updates));
- return true;
+ return writeEntities;
} catch (StatusRuntimeException e) {
- checkAndLogWriteErrors(writeEntities, e, opType, entryType);
- return false;
+ return checkAndLogWriteErrors(writeEntities, e, opType, entryType);
}
}
@@ -1037,8 +1156,9 @@
return null;
}
- private <E extends PiEntity> void checkAndLogWriteErrors(
- Collection<E> writeEntities, StatusRuntimeException ex,
+ // Returns the collection of succesfully write entities.
+ private <T> List<T> checkAndLogWriteErrors(
+ List<T> writeEntities, StatusRuntimeException ex,
WriteOperationType opType, String entryType) {
checkGrpcException(ex);
@@ -1051,30 +1171,32 @@
opType.name(), writeEntities.size(), entryType, deviceId,
ex.getStatus().getCode().name(),
description == null ? "" : " - " + description);
- return;
+ return Collections.emptyList();
}
- // FIXME: we are assuming entities is an ordered collection, e.g. a list,
- // and that errors are reported in the same order as the corresponding
- // written entity. Write RPC methods should be refactored to accept an
- // ordered list of entities, instead of a collection.
if (errors.size() == writeEntities.size()) {
- Iterator<E> entityIterator = writeEntities.iterator();
- errors.stream()
- .map(e -> ImmutablePair.of(e, entityIterator.next()))
- .filter(p -> p.left.getCanonicalCode() != Status.OK.getCode().value())
- .forEach(p -> log.warn("Unable to {} {} on {}: {} [{}]",
- opType.name(), entryType, deviceId,
- parseP4Error(p.getLeft()),
- p.getRight().toString()));
+ List<T> okEntities = Lists.newArrayList();
+ Iterator<T> entityIterator = writeEntities.iterator();
+ for (P4RuntimeOuterClass.Error error : errors) {
+ T entity = entityIterator.next();
+ if (error.getCanonicalCode() != Status.OK.getCode().value()) {
+ log.warn("Unable to {} {} on {}: {} [{}]",
+ opType.name(), entryType, deviceId,
+ parseP4Error(error), entity.toString());
+ } else {
+ okEntities.add(entity);
+ }
+ }
+ return okEntities;
} else {
log.warn("Unable to reconcile error details to updates " +
- "(sent {} updates, but device returned {} errors)",
- entryType, writeEntities.size(), errors.size());
+ "(sent {} updates, but device returned {} errors)",
+ entryType, writeEntities.size(), errors.size());
errors.stream()
.filter(err -> err.getCanonicalCode() != Status.OK.getCode().value())
.forEach(err -> log.warn("Unable to {} {} (unknown): {}",
opType.name(), entryType, parseP4Error(err)));
+ return Collections.emptyList();
}
}
@@ -1099,7 +1221,6 @@
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
-
}
private String parseP4Error(P4RuntimeOuterClass.Error err) {
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
index 53fcf2a..d5d909f 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
@@ -38,11 +38,11 @@
import org.onosproject.net.pi.runtime.PiTableEntry;
import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
import org.slf4j.Logger;
+import p4.config.v1.P4InfoOuterClass;
import p4.v1.P4RuntimeOuterClass.Action;
import p4.v1.P4RuntimeOuterClass.FieldMatch;
import p4.v1.P4RuntimeOuterClass.TableAction;
import p4.v1.P4RuntimeOuterClass.TableEntry;
-import p4.config.v1.P4InfoOuterClass;
import java.util.Collection;
import java.util.Collections;
@@ -82,7 +82,7 @@
* @return collection of P4Runtime table entry protobuf messages
* @throws EncodeException if a PI table entry cannot be encoded
*/
- static Collection<TableEntry> encode(Collection<PiTableEntry> piTableEntries,
+ static List<TableEntry> encode(List<PiTableEntry> piTableEntries,
PiPipeconf pipeconf)
throws EncodeException {
@@ -137,7 +137,7 @@
* @param pipeconf PI pipeconf
* @return collection of PI table entry objects
*/
- static Collection<PiTableEntry> decode(Collection<TableEntry> tableEntryMsgs, PiPipeconf pipeconf) {
+ static List<PiTableEntry> decode(List<TableEntry> tableEntryMsgs, PiPipeconf pipeconf) {
P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
@@ -375,7 +375,7 @@
}
}
- private static PiMatchKey decodeFieldMatchMsgs(Collection<FieldMatch> fieldMatchs, P4InfoOuterClass.Table tableInfo,
+ private static PiMatchKey decodeFieldMatchMsgs(List<FieldMatch> fieldMatchs, P4InfoOuterClass.Table tableInfo,
P4InfoBrowser browser)
throws P4InfoBrowser.NotFoundException, EncodeException {
// Match key for field matches.
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/P4RuntimeGroupTest.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/P4RuntimeGroupTest.java
index 893049f..60913ed 100644
--- a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/P4RuntimeGroupTest.java
+++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/P4RuntimeGroupTest.java
@@ -17,7 +17,6 @@
package org.onosproject.p4runtime.ctl;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.protobuf.ByteString;
import io.grpc.ManagedChannel;
@@ -84,8 +83,8 @@
private static final PiActionParamId PORT_PARAM_ID = PiActionParamId.of("port");
private static final int BASE_MEM_ID = 65535;
private static final List<Integer> MEMBER_IDS = ImmutableList.of(65536, 65537, 65538);
- private static final Collection<PiActionGroupMember> GROUP_MEMBERS =
- ImmutableSet.of(
+ private static final List<PiActionGroupMember> GROUP_MEMBERS =
+ Lists.newArrayList(
outputMember((short) 1),
outputMember((short) 2),
outputMember((short) 3)
@@ -116,6 +115,7 @@
.withParameter(param).build();
return PiActionGroupMember.builder()
+ .forActionProfile(ACT_PROF_ID)
.withAction(piAction)
.withId(PiActionGroupMemberId.of(BASE_MEM_ID + portNum))
.withWeight(DEFAULT_MEMBER_WEIGHT)
@@ -139,7 +139,6 @@
grpcServer = builder.build().start();
grpcChannel = InProcessChannelBuilder.forName(GRPC_SERVER_NAME)
.directExecutor()
- .usePlaintext(true)
.build();
}
@@ -191,7 +190,7 @@
@Test
public void testInsertPiActionMembers() throws Exception {
CompletableFuture<Void> complete = p4RuntimeServerImpl.expectRequests(1);
- client.writeActionGroupMembers(ACT_PROF_ID, GROUP_MEMBERS, INSERT, PIPECONF);
+ client.writeActionGroupMembers(GROUP_MEMBERS, INSERT, PIPECONF);
complete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS);
WriteRequest result = p4RuntimeServerImpl.getWriteReqs().get(0);
assertEquals(1, result.getDeviceId());
@@ -246,6 +245,7 @@
ActionProfileMember actProfMember =
ActionProfileMember.newBuilder()
+ .setActionProfileId(P4_INFO_ACT_PROF_ID)
.setMemberId(id)
.setAction(action)
.build();
@@ -260,13 +260,14 @@
responses.add(ReadResponse.newBuilder()
.addAllEntities(members.stream()
- .map(m -> Entity.newBuilder().setActionProfileMember(m).build())
+ .map(m -> Entity.newBuilder()
+ .setActionProfileMember(m).build())
.collect(Collectors.toList()))
.build());
p4RuntimeServerImpl.willReturnReadResult(responses);
CompletableFuture<Void> complete = p4RuntimeServerImpl.expectRequests(2);
- CompletableFuture<Collection<PiActionGroup>> groupsComplete = client.dumpGroups(ACT_PROF_ID, PIPECONF);
+ CompletableFuture<List<PiActionGroup>> groupsComplete = client.dumpGroups(ACT_PROF_ID, PIPECONF);
complete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS);
Collection<PiActionGroup> groups = groupsComplete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS);