diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java index 77a6dc39f4..fd3b504f57 100644 --- a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java +++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java @@ -38,13 +38,13 @@ import org.onosproject.net.packet.InboundPacket; import org.onosproject.net.packet.OutboundPacket; import org.onosproject.net.pi.model.PiActionId; import org.onosproject.net.pi.model.PiActionParamId; -import org.onosproject.net.pi.model.PiControlMetadataId; import org.onosproject.net.pi.model.PiMatchFieldId; +import org.onosproject.net.pi.model.PiPacketMetadataId; import org.onosproject.net.pi.model.PiPipelineInterpreter; import org.onosproject.net.pi.model.PiTableId; import org.onosproject.net.pi.runtime.PiAction; import org.onosproject.net.pi.runtime.PiActionParam; -import org.onosproject.net.pi.runtime.PiControlMetadata; +import org.onosproject.net.pi.runtime.PiPacketMetadata; import org.onosproject.net.pi.runtime.PiPacketOperation; import java.nio.ByteBuffer; @@ -211,7 +211,7 @@ public final class PipelineInterpreterImpl } @Override - public InboundPacket mapInboundPacket(PiPacketOperation packetIn) + public InboundPacket mapInboundPacket(PiPacketOperation packetIn, DeviceId deviceId) throws PiInterpreterException { // We assume that the packet is ethernet, which is fine since mytunnel.p4 // can deparse only ethernet packets. @@ -225,39 +225,38 @@ public final class PipelineInterpreterImpl } // Returns the ingress port packet metadata. - Optional packetMetadata = packetIn.metadatas().stream() + Optional packetMetadata = packetIn.metadatas().stream() .filter(metadata -> metadata.id().toString().equals(INGRESS_PORT)) .findFirst(); if (packetMetadata.isPresent()) { short s = packetMetadata.get().value().asReadOnlyBuffer().getShort(); ConnectPoint receivedFrom = new ConnectPoint( - packetIn.deviceId(), PortNumber.portNumber(s)); + deviceId, PortNumber.portNumber(s)); return new DefaultInboundPacket( receivedFrom, ethPkt, packetIn.data().asReadOnlyBuffer()); } else { throw new PiInterpreterException(format( "Missing metadata '%s' in packet-in received from '%s': %s", - INGRESS_PORT, packetIn.deviceId(), packetIn)); + INGRESS_PORT, deviceId, packetIn)); } } private PiPacketOperation createPiPacketOp(ByteBuffer data, long portNumber) throws PiInterpreterException { - PiControlMetadata metadata = createControlMetadata(portNumber); + PiPacketMetadata metadata = createPacketMetadata(portNumber); return PiPacketOperation.builder() - .forDevice(this.data().deviceId()) .withType(PACKET_OUT) .withData(copyFrom(data)) .withMetadatas(ImmutableList.of(metadata)) .build(); } - private PiControlMetadata createControlMetadata(long portNumber) + private PiPacketMetadata createPacketMetadata(long portNumber) throws PiInterpreterException { try { - return PiControlMetadata.builder() - .withId(PiControlMetadataId.of(EGRESS_PORT)) + return PiPacketMetadata.builder() + .withId(PiPacketMetadataId.of(EGRESS_PORT)) .withValue(copyFrom(portNumber).fit(PORT_FIELD_BITWIDTH)) .build(); } catch (ImmutableByteSequence.ByteSequenceTrimException e) { diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java index 8043ae7080..48e7b13456 100644 --- a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java +++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java @@ -27,6 +27,7 @@ import org.onosproject.net.driver.AbstractHandlerBehaviour; import org.onosproject.net.pi.model.PiCounterId; import org.onosproject.net.pi.model.PiPipeconf; import org.onosproject.net.pi.runtime.PiCounterCell; +import org.onosproject.net.pi.runtime.PiCounterCellHandle; import org.onosproject.net.pi.runtime.PiCounterCellId; import org.onosproject.net.pi.service.PiPipeconfService; import org.onosproject.p4runtime.api.P4RuntimeClient; @@ -38,7 +39,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.onosproject.net.pi.model.PiCounterType.INDIRECT; @@ -93,16 +93,14 @@ public final class PortStatisticsDiscoveryImpl extends AbstractHandlerBehaviour counterCellIds.add(PiCounterCellId.ofIndirect(INGRESS_COUNTER_ID, p)); counterCellIds.add(PiCounterCellId.ofIndirect(EGRESS_COUNTER_ID, p)); }); + Set counterCellHandles = counterCellIds.stream() + .map(id -> PiCounterCellHandle.of(deviceId, id)) + .collect(Collectors.toSet()); // Query the device. - Collection counterEntryResponse; - try { - counterEntryResponse = client.readCounterCells(counterCellIds, pipeconf).get(); - } catch (InterruptedException | ExecutionException e) { - log.warn("Exception while reading port counters from {}: {}", deviceId, e.toString()); - log.debug("", e); - return Collections.emptyList(); - } + Collection counterEntryResponse = client.read(pipeconf) + .handles(counterCellHandles).submitSync() + .all(PiCounterCell.class); // Process response. counterEntryResponse.forEach(counterCell -> { diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataId.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketMetadataId.java similarity index 68% rename from core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataId.java rename to core/api/src/main/java/org/onosproject/net/pi/model/PiPacketMetadataId.java index b17c7660d9..53cf1b6a72 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataId.java +++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketMetadataId.java @@ -23,24 +23,24 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** - * Identifier of a control metadata in a protocol-independent pipeline, unique within the scope of a pipeline model. + * Identifier of a packet metadata in a protocol-independent pipeline, unique within the scope of a pipeline model. */ @Beta -public final class PiControlMetadataId extends Identifier { +public final class PiPacketMetadataId extends Identifier { - private PiControlMetadataId(String name) { + private PiPacketMetadataId(String name) { super(name); } /** - * Returns an identifier for the given control metadata name. + * Returns an identifier for the given packet metadata name. * - * @param name control metadata name - * @return control metadata ID + * @param name packet metadata name + * @return packet metadata ID */ - public static PiControlMetadataId of(String name) { + public static PiPacketMetadataId of(String name) { checkNotNull(name); checkArgument(!name.isEmpty(), "Name can't be empty"); - return new PiControlMetadataId(name); + return new PiPacketMetadataId(name); } } diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketMetadataModel.java similarity index 82% rename from core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataModel.java rename to core/api/src/main/java/org/onosproject/net/pi/model/PiPacketMetadataModel.java index 3748eacf94..76fdbe462c 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataModel.java +++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketMetadataModel.java @@ -19,17 +19,17 @@ package org.onosproject.net.pi.model; import com.google.common.annotations.Beta; /** - * Model of a control metadata for a protocol-independent pipeline. + * Model of a packet metadata for a protocol-independent pipeline. */ @Beta -public interface PiControlMetadataModel { +public interface PiPacketMetadataModel { /** - * Returns the ID of this control metadata. + * Returns the ID of this packet metadata. * * @return packet operation metadata ID */ - PiControlMetadataId id(); + PiPacketMetadataId id(); /** * Returns the size in bits of this metadata. diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketOperationModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketOperationModel.java index 70659a6627..c48ab6b73d 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketOperationModel.java +++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketOperationModel.java @@ -34,11 +34,11 @@ public interface PiPacketOperationModel { PiPacketOperationType type(); /** - * Returns a list of control metadata models for this packet operation. The metadata models are returned in the same + * Returns a list of packet metadata models for this packet operation. The metadata models are returned in the same * order as they would appear on the control header that is prepended to the packet. * * @return list of packet operation metadata models */ - List metadatas(); + List metadatas(); } diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java index cada0f95ca..1d1301e4fa 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java +++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java @@ -17,6 +17,7 @@ package org.onosproject.net.pi.model; import com.google.common.annotations.Beta; +import org.onosproject.net.DeviceId; import org.onosproject.net.PortNumber; import org.onosproject.net.driver.HandlerBehaviour; import org.onosproject.net.flow.TrafficTreatment; @@ -108,14 +109,16 @@ public interface PiPipelineInterpreter extends HandlerBehaviour { throws PiInterpreterException; /** - * Returns an inbound packet equivalent to the given PI packet operation. + * Returns an inbound packet equivalent to the given PI packet-in operation + * for the given device. * * @param packetOperation packet operation + * @param deviceId ID of the device that originated the packet-in * @return inbound packet * @throws PiInterpreterException if the packet operation cannot be mapped * to an inbound packet */ - InboundPacket mapInboundPacket(PiPacketOperation packetOperation) + InboundPacket mapInboundPacket(PiPacketOperation packetOperation, DeviceId deviceId) throws PiInterpreterException; /** diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileGroup.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileGroup.java index f86e70cb9c..57e65a248a 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileGroup.java +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileGroup.java @@ -21,6 +21,7 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; +import org.onosproject.net.DeviceId; import org.onosproject.net.pi.model.PiActionProfileId; import java.util.Collection; @@ -143,6 +144,11 @@ public final class PiActionProfileGroup implements PiEntity { return PiEntityType.ACTION_PROFILE_GROUP; } + @Override + public PiActionProfileGroupHandle handle(DeviceId deviceId) { + return PiActionProfileGroupHandle.of(deviceId, this); + } + /** * Builder of action profile groups. */ diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileGroupHandle.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileGroupHandle.java index 519a1c4c5e..3579054229 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileGroupHandle.java +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileGroupHandle.java @@ -27,7 +27,7 @@ import org.onosproject.net.pi.model.PiActionProfileId; * defined by a device ID, action profile ID and group ID. */ @Beta -public final class PiActionProfileGroupHandle extends PiHandle { +public final class PiActionProfileGroupHandle extends PiHandle { private final PiActionProfileId actionProfileId; private final PiActionProfileGroupId groupId; @@ -39,10 +39,11 @@ public final class PiActionProfileGroupHandle extends PiHandle { +public final class PiActionProfileMemberHandle extends PiHandle { private final PiActionProfileId actionProfileId; private final PiActionProfileMemberId memberId; diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCell.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCell.java index 9508cbca3e..34ebd7b1a9 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCell.java +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCell.java @@ -19,21 +19,23 @@ package org.onosproject.net.pi.runtime; import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; +import org.onosproject.net.DeviceId; /** * Counter cell of a protocol-independent pipeline. */ @Beta -public final class PiCounterCell { +public final class PiCounterCell implements PiEntity { private final PiCounterCellId cellId; private final PiCounterCellData counterData; /** - * Creates a new counter cell for the given cell identifier and counter cell data. + * Creates a new counter cell for the given cell identifier and counter cell + * data. * - * @param cellId counter cell identifier - * @param piCounterCellData counter cell data + * @param cellId counter cell identifier + * @param piCounterCellData counter cell data */ public PiCounterCell(PiCounterCellId cellId, PiCounterCellData piCounterCellData) { this.cellId = cellId; @@ -41,11 +43,12 @@ public final class PiCounterCell { } /** - * Creates a new counter cell for the given cell identifier, number of packets and bytes. + * Creates a new counter cell for the given cell identifier, number of + * packets and bytes. * * @param cellId counter cell identifier - * @param packets number of packets - * @param bytes number of bytes + * @param packets number of packets + * @param bytes number of bytes */ public PiCounterCell(PiCounterCellId cellId, long packets, long bytes) { this.cellId = cellId; @@ -70,6 +73,16 @@ public final class PiCounterCell { return counterData; } + @Override + public PiEntityType piEntityType() { + return PiEntityType.COUNTER_CELL; + } + + @Override + public PiCounterCellHandle handle(DeviceId deviceId) { + return PiCounterCellHandle.of(deviceId, this); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellHandle.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellHandle.java new file mode 100644 index 0000000000..c06fa528f1 --- /dev/null +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellHandle.java @@ -0,0 +1,99 @@ +/* + * Copyright 2019-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 static com.google.common.base.Preconditions.checkNotNull; + +/** + * Global identifier of a PI counter cell instantiated on a device, uniquely + * defined by a device ID and cell ID. + */ +public final class PiCounterCellHandle extends PiHandle { + + private final PiCounterCellId cellId; + + private PiCounterCellHandle(DeviceId deviceId, PiCounterCellId cellId) { + super(deviceId); + this.cellId = checkNotNull(cellId); + } + + /** + * Creates a new handle for the given device ID and counter cell ID. + * + * @param deviceId device ID + * @param cellId counter cell ID + * @return new counter cell handle + */ + public static PiCounterCellHandle of(DeviceId deviceId, PiCounterCellId cellId) { + return new PiCounterCellHandle(deviceId, cellId); + } + + /** + * Creates a new handle for the given device ID and counter cell. + * + * @param deviceId device ID + * @param cell counter cell + * @return new counter cell handle + */ + public static PiCounterCellHandle of(DeviceId deviceId, PiCounterCell cell) { + return new PiCounterCellHandle(deviceId, cell.cellId()); + } + + /** + * Returns the counter cell ID associated with this handle. + * + * @return counter cell ID + */ + public PiCounterCellId cellId() { + return cellId; + } + + @Override + public PiEntityType entityType() { + return PiEntityType.COUNTER_CELL; + } + + @Override + public int hashCode() { + return Objects.hashCode(deviceId(), cellId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final PiCounterCellHandle other = (PiCounterCellHandle) obj; + return Objects.equal(this.deviceId(), other.deviceId()) + && Objects.equal(this.cellId, other.cellId); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("deviceId", deviceId()) + .add("cellId", cellId) + .toString(); + } +} diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntity.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntity.java index c3d5a01a05..531a6a2b96 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntity.java +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntity.java @@ -17,6 +17,7 @@ package org.onosproject.net.pi.runtime; import com.google.common.annotations.Beta; +import org.onosproject.net.DeviceId; /** * Abstraction of an entity of a protocol-independent that can be read or write @@ -31,4 +32,12 @@ public interface PiEntity { * @return entity type */ PiEntityType piEntityType(); + + /** + * Returns a handle for this PI entity and the given device ID. + * + * @param deviceId device ID + * @return handle + */ + PiHandle handle(DeviceId deviceId); } diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java index 4c318302eb..0e991888c4 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java @@ -26,35 +26,56 @@ public enum PiEntityType { /** * Table entry. */ - TABLE_ENTRY, + TABLE_ENTRY("table entry"), /** * Action profile group. */ - ACTION_PROFILE_GROUP, + ACTION_PROFILE_GROUP("action profile group"), /** * Action profile member. */ - ACTION_PROFILE_MEMBER, + ACTION_PROFILE_MEMBER("action profile member"), /** - * Meter config. + * Meter cell config. */ - METER_CELL_CONFIG, + METER_CELL_CONFIG("meter cell config"), /** - * Register entry. + * Register cell. */ - REGISTER_CELL, + REGISTER_CELL("register cell"), + + /** + * Counter cell. + */ + COUNTER_CELL("counter cell"), /** * Packet Replication Engine (PRE) multicast group entry. */ - PRE_MULTICAST_GROUP_ENTRY, + PRE_MULTICAST_GROUP_ENTRY("PRE multicast group entry"), /** * Packet Replication Engine (PRE) clone session entry. */ - PRE_CLONE_SESSION_ENTRY + PRE_CLONE_SESSION_ENTRY("PRE clone session entry"); + + private final String humanReadableName; + + PiEntityType(String humanReadableName) { + this.humanReadableName = humanReadableName; + } + + /** + * Returns a human readable representation of this PI entity type (useful + * for logging). + * + * @return string + */ + public String humanReadableName() { + return humanReadableName; + } } 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 eb74288243..c95909624c 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 @@ -26,7 +26,7 @@ import static com.google.common.base.Preconditions.checkNotNull; * the whole network. */ @Beta -public abstract class PiHandle { +public abstract class PiHandle { private final DeviceId deviceId; diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMatchKey.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMatchKey.java index a978bfa6dd..4ce849ff9b 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMatchKey.java +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMatchKey.java @@ -31,7 +31,7 @@ import java.util.StringJoiner; @Beta public final class PiMatchKey { - public static final PiMatchKey EMPTY = builder().build(); + public static final PiMatchKey EMPTY = new PiMatchKey(ImmutableMap.of()); private final ImmutableMap fieldMatches; diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterBand.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterBand.java index efa53ffc20..b1dbcf5cc1 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterBand.java +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterBand.java @@ -26,7 +26,7 @@ import static com.google.common.base.MoreObjects.toStringHelper; * Represents a band used within a meter. */ @Beta -public class PiMeterBand { +public final class PiMeterBand { private final long rate; private final long burst; diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterCellConfig.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterCellConfig.java index 96ba124dae..f13e276af3 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterCellConfig.java +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterCellConfig.java @@ -20,6 +20,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; +import org.onosproject.net.DeviceId; import java.util.ArrayList; import java.util.Collection; @@ -70,6 +71,11 @@ public final class PiMeterCellConfig implements PiEntity { return PiEntityType.METER_CELL_CONFIG; } + @Override + public PiMeterCellHandle handle(DeviceId deviceId) { + return PiMeterCellHandle.of(deviceId, this); + } + @Override public boolean equals(Object o) { if (this == o) { 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/PiMeterCellHandle.java similarity index 72% rename from core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterHandle.java rename to core/api/src/main/java/org/onosproject/net/pi/runtime/PiMeterCellHandle.java index 4baa6fa19d..382f60be0a 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/PiMeterCellHandle.java @@ -24,15 +24,15 @@ 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 instantiated on a device, uniquely + * defined by a device ID and meter cell ID. */ @Beta -public final class PiMeterHandle extends PiHandle { +public final class PiMeterCellHandle extends PiHandle { private final PiMeterCellId cellId; - private PiMeterHandle(DeviceId deviceId, PiMeterCellId meterCellId) { + private PiMeterCellHandle(DeviceId deviceId, PiMeterCellId meterCellId) { super(deviceId); this.cellId = meterCellId; } @@ -44,9 +44,9 @@ public final class PiMeterHandle extends PiHandle { * @param meterCellId meter cell ID * @return PI meter handle */ - public static PiMeterHandle of(DeviceId deviceId, - PiMeterCellId meterCellId) { - return new PiMeterHandle(deviceId, meterCellId); + public static PiMeterCellHandle of(DeviceId deviceId, + PiMeterCellId meterCellId) { + return new PiMeterCellHandle(deviceId, meterCellId); } /** @@ -57,10 +57,19 @@ public final class PiMeterHandle extends PiHandle { * @param meterCellConfig meter config * @return PI meter handle */ - public static PiMeterHandle of(DeviceId deviceId, - PiMeterCellConfig meterCellConfig) { + public static PiMeterCellHandle of(DeviceId deviceId, + PiMeterCellConfig meterCellConfig) { checkNotNull(meterCellConfig); - return new PiMeterHandle(deviceId, meterCellConfig.cellId()); + return new PiMeterCellHandle(deviceId, meterCellConfig.cellId()); + } + + /** + * Returns the cell ID associated with this handle. + * + * @return cell ID + */ + public PiMeterCellId cellId() { + return cellId; } @Override @@ -81,7 +90,7 @@ public final class PiMeterHandle extends PiHandle { if (o == null || getClass() != o.getClass()) { return false; } - PiMeterHandle that = (PiMeterHandle) o; + PiMeterCellHandle that = (PiMeterCellHandle) o; return Objects.equal(deviceId(), that.deviceId()) && Objects.equal(cellId, that.cellId); } diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntry.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntry.java index 41b93f60a7..7a833aa7f0 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntry.java +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntry.java @@ -20,6 +20,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; +import org.onosproject.net.DeviceId; import java.util.Collection; import java.util.Set; @@ -70,6 +71,11 @@ public final class PiMulticastGroupEntry implements PiPreEntry { return PiEntityType.PRE_MULTICAST_GROUP_ENTRY; } + @Override + public PiMulticastGroupEntryHandle handle(DeviceId deviceId) { + return PiMulticastGroupEntryHandle.of(deviceId, this); + } + @Override public int hashCode() { return Objects.hashCode(groupId, replicas); diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntryHandle.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMulticastGroupEntryHandle.java index 65a3f28c79..b74ca8e34e 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 @@ -29,11 +29,11 @@ import static com.google.common.base.Preconditions.checkNotNull; * ID. */ @Beta -public final class PiMulticastGroupEntryHandle extends PiHandle { +public final class PiMulticastGroupEntryHandle extends PiHandle { - private final long groupId; + private final int groupId; - private PiMulticastGroupEntryHandle(DeviceId deviceId, long groupId) { + private PiMulticastGroupEntryHandle(DeviceId deviceId, int groupId) { super(deviceId); this.groupId = groupId; } @@ -46,7 +46,7 @@ public final class PiMulticastGroupEntryHandle extends PiHandle packetMetadatas; + private final ImmutableByteSequence frame; + private final Set packetMetadatas; private final PiPacketOperationType type; /** - * Creates a new packet I/O operation for the given device ID, data, control metadatas and operation type. + * Creates a new packet I/O operation for the given frame, packet metadata + * and operation type. * - * @param deviceId device ID - * @param data the packet raw data - * @param packetMetadatas collection of control metadata - * @param type type of this packet operation + * @param frame the packet raw data + * @param metadatas collection of packet metadata + * @param type type of this packet operation */ - private PiPacketOperation(DeviceId deviceId, ImmutableByteSequence data, - Collection packetMetadatas, + private PiPacketOperation(ImmutableByteSequence frame, + Collection metadatas, PiPacketOperationType type) { - this.deviceId = deviceId; - this.data = data; - this.packetMetadatas = ImmutableSet.copyOf(packetMetadatas); + this.frame = frame; + this.packetMetadatas = ImmutableSet.copyOf(metadatas); this.type = type; } - /** - * Returns the device ID of this packet operation. - * - * @return device ID - */ - public DeviceId deviceId() { - return deviceId; - } - /** * Return the type of this packet. * @@ -84,15 +73,16 @@ public final class PiPacketOperation { * @return packet data */ public ImmutableByteSequence data() { - return data; + return frame; } /** - * Returns all metadatas of this packet. Returns an empty collection if the packet doesn't have any metadata. + * Returns all metadatas of this packet. Returns an empty collection if the + * packet doesn't have any metadata. * * @return collection of metadatas */ - public Collection metadatas() { + public Collection metadatas() { return packetMetadatas; } @@ -106,22 +96,21 @@ public final class PiPacketOperation { } PiPacketOperation that = (PiPacketOperation) o; return Objects.equal(packetMetadatas, that.packetMetadatas) && - Objects.equal(deviceId, that.deviceId) && - Objects.equal(data, that.data()) && + Objects.equal(frame, that.data()) && Objects.equal(type, that.type()); } @Override public int hashCode() { - return Objects.hashCode(deviceId, data, packetMetadatas, type); + return Objects.hashCode(frame, packetMetadatas, type); } @Override public String toString() { return MoreObjects.toStringHelper(this) - .add("deviceId", deviceId) - .addValue(type.toString()) - .addValue(packetMetadatas) + .add("type", type) + .add("metadata", packetMetadatas) + .add("frame", frame) .toString(); } @@ -139,8 +128,7 @@ public final class PiPacketOperation { */ public static final class Builder { - private DeviceId deviceId; - private Map packetMetadatas = new HashMap<>(); + private Map packetMetadatas = new HashMap<>(); private PiPacketOperationType type; private ImmutableByteSequence data; @@ -148,18 +136,6 @@ public final class PiPacketOperation { // hides constructor. } - /** - * Sets the device ID. - * - * @param deviceId device ID - * @return this - */ - public Builder forDevice(DeviceId deviceId) { - checkNotNull(deviceId); - this.deviceId = deviceId; - return this; - } - /** * Sets the raw packet data. * @@ -173,13 +149,14 @@ public final class PiPacketOperation { } /** - * Adds a control metadata. Only one metadata is allowed for a given metadata id. If a metadata with same id - * already exists it will be replaced by the given one. + * Adds a packet metadata. Only one metadata is allowed for a given + * metadata id. If a metadata with same id already exists it will be + * replaced by the given one. * * @param metadata packet metadata * @return this */ - public Builder withMetadata(PiControlMetadata metadata) { + public Builder withMetadata(PiPacketMetadata metadata) { checkNotNull(metadata); packetMetadatas.put(metadata.id(), metadata); @@ -192,7 +169,7 @@ public final class PiPacketOperation { * @param metadatas collection of metadata * @return this */ - public Builder withMetadatas(Collection metadatas) { + public Builder withMetadatas(Collection metadatas) { checkNotNull(metadatas); metadatas.forEach(this::withMetadata); return this; @@ -215,11 +192,10 @@ public final class PiPacketOperation { * @return packet operation */ public PiPacketOperation build() { - checkNotNull(deviceId); checkNotNull(data); checkNotNull(packetMetadatas); checkNotNull(type); - return new PiPacketOperation(deviceId, data, packetMetadatas.values(), type); + return new PiPacketOperation(data, packetMetadatas.values(), type); } } } diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPreReplica.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPreReplica.java index 4f65e4d6c0..3a03feb16d 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPreReplica.java +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPreReplica.java @@ -16,6 +16,7 @@ package org.onosproject.net.pi.runtime; +import com.google.common.annotations.Beta; import com.google.common.base.Objects; import org.onosproject.net.PortNumber; @@ -29,7 +30,8 @@ import static java.lang.String.format; * Each replica is uniquely identified inside a given multicast group or clone * session by the pair (egress port, instance ID). */ -public class PiPreReplica { +@Beta +public final class PiPreReplica { private final PortNumber egressPort; private final int instanceId; diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiRegisterCell.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiRegisterCell.java index fd354ecb64..c50771b819 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiRegisterCell.java +++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiRegisterCell.java @@ -19,6 +19,7 @@ package org.onosproject.net.pi.runtime; import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; +import org.onosproject.net.DeviceId; import org.onosproject.net.pi.model.PiData; import static com.google.common.base.Preconditions.checkNotNull; @@ -60,6 +61,12 @@ public final class PiRegisterCell implements PiEntity { return PiEntityType.REGISTER_CELL; } + @Override + public PiHandle handle(DeviceId deviceId) { + // TODO: implement support for register cell handles + throw new UnsupportedOperationException("not implemented"); + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -137,4 +144,4 @@ public final class PiRegisterCell implements PiEntity { return new PiRegisterCell(cellId, piData); } } -} \ No newline at end of file +} 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 b25ada5033..ee3c3c3e12 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 @@ -19,9 +19,11 @@ package org.onosproject.net.pi.runtime; import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; +import org.onosproject.net.DeviceId; import org.onosproject.net.pi.model.PiTableId; import java.util.Optional; +import java.util.OptionalInt; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -112,8 +114,8 @@ public final class PiTableEntry implements PiEntity { * * @return optional priority */ - public Optional priority() { - return priority == NO_PRIORITY ? Optional.empty() : Optional.of(priority); + public OptionalInt priority() { + return priority == NO_PRIORITY ? OptionalInt.empty() : OptionalInt.of(priority); } /** @@ -203,6 +205,11 @@ public final class PiTableEntry implements PiEntity { return PiEntityType.TABLE_ENTRY; } + @Override + public PiTableEntryHandle handle(DeviceId deviceId) { + return PiTableEntryHandle.of(deviceId, this); + } + public static final class Builder { private PiTableId tableId; 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 2b210a1cbd..b4edb3cd56 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 @@ -22,6 +22,8 @@ import com.google.common.base.Objects; import org.onosproject.net.DeviceId; import org.onosproject.net.pi.model.PiTableId; +import java.util.OptionalInt; + import static com.google.common.base.Preconditions.checkNotNull; /** @@ -29,30 +31,20 @@ import static com.google.common.base.Preconditions.checkNotNull; * by a device ID, table ID and match key. */ @Beta -public final class PiTableEntryHandle extends PiHandle { +public final class PiTableEntryHandle extends PiHandle { + + private static final int NO_PRIORITY = -1; private final PiTableId tableId; private final PiMatchKey matchKey; + private final int priority; - private PiTableEntryHandle(DeviceId deviceId, PiTableId tableId, PiMatchKey matchKey) { + private PiTableEntryHandle(DeviceId deviceId, PiTableId tableId, PiMatchKey matchKey, + Integer priority) { 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); + this.priority = priority; } /** @@ -64,7 +56,37 @@ public final class PiTableEntryHandle extends PiHandle { */ public static PiTableEntryHandle of(DeviceId deviceId, PiTableEntry entry) { checkNotNull(entry); - return PiTableEntryHandle.of(deviceId, entry.table(), entry.matchKey()); + return new PiTableEntryHandle( + deviceId, entry.table(), entry.matchKey(), + entry.priority().orElse(NO_PRIORITY)); + } + + /** + * Returns the table ID associated with this handle. + * + * @return table ID + */ + public PiTableId tableId() { + return tableId; + } + + /** + * Returns the match key associated with this handle. + * + * @return match key + */ + public PiMatchKey matchKey() { + return matchKey; + } + + /** + * Returns the optional priority associated with this handle. + * + * @return optional priority + */ + public OptionalInt priority() { + return priority == NO_PRIORITY + ? OptionalInt.empty() : OptionalInt.of(priority); } @Override @@ -74,7 +96,7 @@ public final class PiTableEntryHandle extends PiHandle { @Override public int hashCode() { - return Objects.hashCode(deviceId(), tableId, matchKey); + return Objects.hashCode(deviceId(), tableId, matchKey, priority().orElse(NO_PRIORITY)); } @Override @@ -88,7 +110,8 @@ public final class PiTableEntryHandle extends PiHandle { final PiTableEntryHandle other = (PiTableEntryHandle) obj; return Objects.equal(this.deviceId(), other.deviceId()) && Objects.equal(this.tableId, other.tableId) - && Objects.equal(this.matchKey, other.matchKey); + && Objects.equal(this.matchKey, other.matchKey) + && Objects.equal(this.priority(), other.priority()); } @Override @@ -97,6 +120,7 @@ public final class PiTableEntryHandle extends PiHandle { .add("deviceId", deviceId()) .add("tableId", tableId) .add("matchKey", matchKey) + .add("priority", priority == NO_PRIORITY ? "N/A" : priority) .toString(); } } diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiPipeconfService.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiPipeconfService.java index e9539a59c3..994335279b 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/service/PiPipeconfService.java +++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiPipeconfService.java @@ -70,6 +70,16 @@ public interface PiPipeconfService { */ Optional getPipeconf(PiPipeconfId id); + /** + * Returns the pipeconf instance associated with the given device, if + * present. If not present, it means no pipeconf has been associated with + * that device so far. + * + * @param deviceId a device identifier + * @return an optional pipeconf + */ + Optional getPipeconf(DeviceId deviceId); + /** * Signals that the given pipeconf is associated to the given infrastructure * device. As a result of this method, the pipeconf for the given device can @@ -107,7 +117,9 @@ public interface PiPipeconfService { * * @param deviceId device identifier * @return an optional pipeconf identifier + * @deprecated in ONOS 2.1 use {@link #getPipeconf(DeviceId)} instead */ + @Deprecated Optional ofDevice(DeviceId deviceId); } diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslatedEntity.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslatedEntity.java index 4ca094bec5..3c3d9f6fbe 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslatedEntity.java +++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslatedEntity.java @@ -32,7 +32,7 @@ public final class PiTranslatedEntity handle; + private final PiHandle handle; /** * Creates a new translated entity. @@ -41,7 +41,7 @@ public final class PiTranslatedEntity handle) { + public PiTranslatedEntity(T original, E translated, PiHandle handle) { this.original = checkNotNull(original); this.translated = checkNotNull(translated); this.handle = checkNotNull(handle); @@ -79,7 +79,7 @@ public final class PiTranslatedEntity handle() { + public final PiHandle handle() { return handle; } } diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationStore.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationStore.java index 6274debbe9..a1f3a60fdc 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationStore.java +++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationStore.java @@ -39,7 +39,7 @@ public interface PiTranslationStore handle, PiTranslatedEntity entity); + void addOrUpdate(PiHandle handle, PiTranslatedEntity entity); /** * Returns a PI translated entity for the given handle. Returns null if this @@ -49,12 +49,12 @@ public interface PiTranslationStore get(PiHandle handle); + PiTranslatedEntity get(PiHandle handle); /** * Removes a previously added mapping for the given PI entity handle. * * @param handle PI entity handle */ - void remove(PiHandle handle); + void remove(PiHandle handle); } diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslator.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslator.java index 202636abe6..499fdc5027 100644 --- a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslator.java +++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslator.java @@ -52,7 +52,7 @@ public interface PiTranslator { * @param handle PI entity handle * @param entity PI translated entity */ - void learn(PiHandle handle, PiTranslatedEntity entity); + void learn(PiHandle handle, PiTranslatedEntity entity); /** * Returns a PI translated entity that was previously associated with the @@ -64,12 +64,12 @@ public interface PiTranslator { * @param handle PI entity handle * @return optional PI translated entity */ - Optional> lookup(PiHandle handle); + Optional> lookup(PiHandle handle); /** * Removes any mapping for the given PI entity handle. * * @param handle PI entity handle. */ - void forget(PiHandle handle); + void forget(PiHandle handle); } diff --git a/core/api/src/test/java/org/onosproject/net/pi/PiPipeconfServiceAdapter.java b/core/api/src/test/java/org/onosproject/net/pi/PiPipeconfServiceAdapter.java index 8eebf5fe0a..66f9315f5d 100644 --- a/core/api/src/test/java/org/onosproject/net/pi/PiPipeconfServiceAdapter.java +++ b/core/api/src/test/java/org/onosproject/net/pi/PiPipeconfServiceAdapter.java @@ -48,6 +48,11 @@ public class PiPipeconfServiceAdapter implements PiPipeconfService { return Optional.empty(); } + @Override + public Optional getPipeconf(DeviceId deviceId) { + return Optional.empty(); + } + @Override public void bindToDevice(PiPipeconfId pipeconfId, DeviceId deviceId) { diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataIdTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataIdTest.java similarity index 62% rename from core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataIdTest.java rename to core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataIdTest.java index d65ef2eb1b..310b8363c8 100644 --- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataIdTest.java +++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataIdTest.java @@ -18,7 +18,7 @@ package org.onosproject.net.pi.runtime; import com.google.common.testing.EqualsTester; import org.junit.Test; -import org.onosproject.net.pi.model.PiControlMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataId; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -28,21 +28,21 @@ import static org.onosproject.net.pi.runtime.PiConstantsTest.EGRESS_PORT; import static org.onosproject.net.pi.runtime.PiConstantsTest.INGRESS_PORT; /** - * Unit tests for PiControlMetadataId class. + * Unit tests for PiPacketMetadataId class. */ -public class PiControlMetadataIdTest { +public class PiPacketMetadataIdTest { - final PiControlMetadataId piControlMetadataId1 = PiControlMetadataId.of(EGRESS_PORT); - final PiControlMetadataId sameAsPiControlMetadataId1 = PiControlMetadataId.of(EGRESS_PORT); - final PiControlMetadataId piControlMetadataId2 = PiControlMetadataId.of(INGRESS_PORT); + final PiPacketMetadataId piPacketMetadataId1 = PiPacketMetadataId.of(EGRESS_PORT); + final PiPacketMetadataId sameAsPiPacketMetadataId1 = PiPacketMetadataId.of(EGRESS_PORT); + final PiPacketMetadataId piPacketMetadataId2 = PiPacketMetadataId.of(INGRESS_PORT); /** - * Checks that the PiControlMetadataId class is immutable. + * Checks that the PiPacketMetadataId class is immutable. */ @Test public void testImmutability() { - assertThatClassIsImmutable(PiControlMetadataId.class); + assertThatClassIsImmutable(PiPacketMetadataId.class); } /** @@ -52,17 +52,17 @@ public class PiControlMetadataIdTest { public void testEquals() { new EqualsTester() - .addEqualityGroup(piControlMetadataId1, sameAsPiControlMetadataId1) - .addEqualityGroup(piControlMetadataId2) + .addEqualityGroup(piPacketMetadataId1, sameAsPiPacketMetadataId1) + .addEqualityGroup(piPacketMetadataId2) .testEquals(); } /** - * Checks the methods of PiControlMetadataId. + * Checks the methods of PiPacketMetadataId. */ @Test public void testMethods() { - assertThat(piControlMetadataId1, is(notNullValue())); - assertThat(piControlMetadataId1.id(), is(EGRESS_PORT)); + assertThat(piPacketMetadataId1, is(notNullValue())); + assertThat(piPacketMetadataId1.id(), is(EGRESS_PORT)); } } diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataTest.java similarity index 59% rename from core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataTest.java rename to core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataTest.java index ab80fd60a2..58cd252727 100644 --- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataTest.java +++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataTest.java @@ -18,7 +18,7 @@ package org.onosproject.net.pi.runtime; import com.google.common.testing.EqualsTester; import org.junit.Test; -import org.onosproject.net.pi.model.PiControlMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataId; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -28,32 +28,32 @@ import static org.onlab.util.ImmutableByteSequence.copyFrom; import static org.onosproject.net.pi.runtime.PiConstantsTest.EGRESS_PORT; /** - * Unit tests for PiControlMetadata class. + * Unit tests for PiPacketMetadata class. */ -public class PiControlMetadataTest { +public class PiPacketMetadataTest { - final PiControlMetadataId piControlMetadataId = PiControlMetadataId.of(EGRESS_PORT); + final PiPacketMetadataId piPacketMetadataId = PiPacketMetadataId.of(EGRESS_PORT); - final PiControlMetadata piControlMetadata1 = PiControlMetadata.builder() - .withId(piControlMetadataId) + final PiPacketMetadata piPacketMetadata1 = PiPacketMetadata.builder() + .withId(piPacketMetadataId) .withValue(copyFrom(0x10)) .build(); - final PiControlMetadata sameAsPiControlMetadata1 = PiControlMetadata.builder() - .withId(piControlMetadataId) + final PiPacketMetadata sameAsPiPacketMetadata1 = PiPacketMetadata.builder() + .withId(piPacketMetadataId) .withValue(copyFrom(0x10)) .build(); - final PiControlMetadata piControlMetadata2 = PiControlMetadata.builder() - .withId(piControlMetadataId) + final PiPacketMetadata piPacketMetadata2 = PiPacketMetadata.builder() + .withId(piPacketMetadataId) .withValue(copyFrom(0x20)) .build(); /** - * Checks that the PiControlMetadata class is immutable. + * Checks that the PiPacketMetadata class is immutable. */ @Test public void testImmutability() { - assertThatClassIsImmutable(PiControlMetadata.class); + assertThatClassIsImmutable(PiPacketMetadata.class); } /** @@ -63,19 +63,19 @@ public class PiControlMetadataTest { public void testEquals() { new EqualsTester() - .addEqualityGroup(piControlMetadata1, sameAsPiControlMetadata1) - .addEqualityGroup(piControlMetadata2) + .addEqualityGroup(piPacketMetadata1, sameAsPiPacketMetadata1) + .addEqualityGroup(piPacketMetadata2) .testEquals(); } /** - * Checks the methods of PiControlMetadata. + * Checks the methods of PiPacketMetadata. */ @Test public void testMethods() { - assertThat(piControlMetadata1, is(notNullValue())); - assertThat(piControlMetadata1.id(), is(PiControlMetadataId.of(EGRESS_PORT))); - assertThat(piControlMetadata1.value(), is(copyFrom(0x10))); + assertThat(piPacketMetadata1, is(notNullValue())); + assertThat(piPacketMetadata1.id(), is(PiPacketMetadataId.of(EGRESS_PORT))); + assertThat(piPacketMetadata1.value(), is(copyFrom(0x10))); } } diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketOperationTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketOperationTest.java index d675b51205..e924914a73 100644 --- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketOperationTest.java +++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketOperationTest.java @@ -21,8 +21,7 @@ import com.google.common.testing.EqualsTester; import org.apache.commons.collections.CollectionUtils; import org.junit.Test; import org.onlab.util.ImmutableByteSequence; -import org.onosproject.net.DeviceId; -import org.onosproject.net.pi.model.PiControlMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataId; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -37,34 +36,29 @@ import static org.onosproject.net.pi.runtime.PiConstantsTest.EGRESS_PORT; */ public class PiPacketOperationTest { - private final DeviceId deviceId = DeviceId.deviceId("dummy"); - private final PiPacketOperation piPacketOperation1 = PiPacketOperation.builder() - .forDevice(deviceId) .withData(ImmutableByteSequence.ofOnes(512)) .withType(PACKET_OUT) - .withMetadata(PiControlMetadata.builder() - .withId(PiControlMetadataId.of(EGRESS_PORT)) + .withMetadata(PiPacketMetadata.builder() + .withId(PiPacketMetadataId.of(EGRESS_PORT)) .withValue(copyFrom((short) 255)) .build()) .build(); private final PiPacketOperation sameAsPiPacketOperation1 = PiPacketOperation.builder() - .forDevice(deviceId) .withData(ImmutableByteSequence.ofOnes(512)) .withType(PACKET_OUT) - .withMetadata(PiControlMetadata.builder() - .withId(PiControlMetadataId.of(EGRESS_PORT)) + .withMetadata(PiPacketMetadata.builder() + .withId(PiPacketMetadataId.of(EGRESS_PORT)) .withValue(copyFrom((short) 255)) .build()) .build(); private final PiPacketOperation piPacketOperation2 = PiPacketOperation.builder() - .forDevice(deviceId) .withData(ImmutableByteSequence.ofOnes(512)) .withType(PACKET_OUT) - .withMetadata(PiControlMetadata.builder() - .withId(PiControlMetadataId.of(EGRESS_PORT)) + .withMetadata(PiPacketMetadata.builder() + .withId(PiPacketMetadataId.of(EGRESS_PORT)) .withValue(copyFrom((short) 200)) .build()) .build(); @@ -97,23 +91,21 @@ public class PiPacketOperationTest { public void testMethods() { final PiPacketOperation piPacketOperation = PiPacketOperation.builder() - .forDevice(deviceId) .withData(ImmutableByteSequence.ofOnes(512)) .withType(PACKET_OUT) - .withMetadata(PiControlMetadata.builder() - .withId(PiControlMetadataId.of(EGRESS_PORT)) + .withMetadata(PiPacketMetadata.builder() + .withId(PiPacketMetadataId.of(EGRESS_PORT)) .withValue(copyFrom((short) 10)) .build()) .build(); assertThat(piPacketOperation, is(notNullValue())); - assertThat(piPacketOperation.deviceId(), is(deviceId)); assertThat(piPacketOperation.type(), is(PACKET_OUT)); assertThat(piPacketOperation.data(), is(ImmutableByteSequence.ofOnes(512))); assertThat("Incorrect metadatas value", CollectionUtils.isEqualCollection(piPacketOperation.metadatas(), - ImmutableList.of(PiControlMetadata.builder() - .withId(PiControlMetadataId + ImmutableList.of(PiPacketMetadata.builder() + .withId(PiPacketMetadataId .of(EGRESS_PORT)) .withValue(copyFrom((short) 10)) .build()))); diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableEntryTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableEntryTest.java index 087eb77be1..ee2fd7887b 100644 --- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableEntryTest.java +++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableEntryTest.java @@ -110,7 +110,7 @@ public class PiTableEntryTest { assertThat(piTableEntry.cookie(), is(cookie)); assertThat("Priority must be set", piTableEntry.priority().isPresent()); assertThat("Timeout must be set", piTableEntry.timeout().isPresent()); - assertThat(piTableEntry.priority().get(), is(priority)); + assertThat(piTableEntry.priority().getAsInt(), is(priority)); assertThat(piTableEntry.timeout().get(), is(timeout)); assertThat("Incorrect match param value", CollectionUtils.isEqualCollection(piTableEntry.matchKey().fieldMatches(), fieldMatches.values())); diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/AbstractPiTranslatorImpl.java b/core/net/src/main/java/org/onosproject/net/pi/impl/AbstractPiTranslatorImpl.java index 5d7178c645..dc2cb010eb 100644 --- a/core/net/src/main/java/org/onosproject/net/pi/impl/AbstractPiTranslatorImpl.java +++ b/core/net/src/main/java/org/onosproject/net/pi/impl/AbstractPiTranslatorImpl.java @@ -42,17 +42,17 @@ public abstract class AbstractPiTranslatorImpl } @Override - public void learn(PiHandle handle, PiTranslatedEntity entity) { + public void learn(PiHandle handle, PiTranslatedEntity entity) { store.addOrUpdate(handle, entity); } @Override - public Optional> lookup(PiHandle handle) { + public Optional> lookup(PiHandle handle) { return Optional.ofNullable(store.get(handle)); } @Override - public void forget(PiHandle handle) { + public void forget(PiHandle handle) { store.remove(handle); } } diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java index fa10d3374c..27f6e4dfb0 100644 --- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java +++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java @@ -168,6 +168,16 @@ public class PiPipeconfManager implements PiPipeconfService { return Optional.ofNullable(pipeconfs.get(id)); } + @Override + public Optional getPipeconf(DeviceId deviceId) { + if (pipeconfMappingStore.getPipeconfId(deviceId) == null) { + return Optional.empty(); + } else { + return Optional.ofNullable(pipeconfs.get( + pipeconfMappingStore.getPipeconfId(deviceId))); + } + } + @Override public void bindToDevice(PiPipeconfId pipeconfId, DeviceId deviceId) { PiPipeconfId existingPipeconfId = pipeconfMappingStore.getPipeconfId(deviceId); diff --git a/core/store/dist/src/main/java/org/onosproject/store/pi/impl/AbstractDistributedPiTranslationStore.java b/core/store/dist/src/main/java/org/onosproject/store/pi/impl/AbstractDistributedPiTranslationStore.java index 36d87b5ff3..21a39bf892 100644 --- a/core/store/dist/src/main/java/org/onosproject/store/pi/impl/AbstractDistributedPiTranslationStore.java +++ b/core/store/dist/src/main/java/org/onosproject/store/pi/impl/AbstractDistributedPiTranslationStore.java @@ -56,11 +56,11 @@ public abstract class AbstractDistributedPiTranslationStore @Reference(cardinality = ReferenceCardinality.MANDATORY) protected StorageService storageService; - private EventuallyConsistentMap, PiTranslatedEntity> + private EventuallyConsistentMap> translatedEntities; private final EventuallyConsistentMapListener - , PiTranslatedEntity> entityMapListener = + > entityMapListener = new InternalEntityMapListener(); /** @@ -75,7 +75,7 @@ public abstract class AbstractDistributedPiTranslationStore public void activate() { final String fullMapName = format(MAP_NAME_TEMPLATE, mapSimpleName()); translatedEntities = storageService - ., PiTranslatedEntity>eventuallyConsistentMapBuilder() + .>eventuallyConsistentMapBuilder() .withName(fullMapName) .withSerializer(KryoNamespaces.API) .withTimestampProvider((k, v) -> new WallClockTimestamp()) @@ -92,7 +92,7 @@ public abstract class AbstractDistributedPiTranslationStore } @Override - public void addOrUpdate(PiHandle handle, PiTranslatedEntity entity) { + public void addOrUpdate(PiHandle handle, PiTranslatedEntity entity) { checkNotNull(handle); checkNotNull(entity); checkArgument(handle.entityType().equals(entity.entityType()), @@ -101,13 +101,13 @@ public abstract class AbstractDistributedPiTranslationStore } @Override - public void remove(PiHandle handle) { + public void remove(PiHandle handle) { checkNotNull(handle); translatedEntities.remove(handle); } @Override - public PiTranslatedEntity get(PiHandle handle) { + public PiTranslatedEntity get(PiHandle handle) { checkNotNull(handle); return translatedEntities.get(handle); } @@ -118,10 +118,10 @@ public abstract class AbstractDistributedPiTranslationStore private class InternalEntityMapListener implements EventuallyConsistentMapListener - , PiTranslatedEntity> { + > { @Override - public void event(EventuallyConsistentMapEvent, + public void event(EventuallyConsistentMapEvent> event) { final PiTranslationEvent.Type type; switch (event.type()) { 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 acfce12f87..fea739222a 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 @@ -43,9 +43,8 @@ public class DistributedPiTranslationStoreTest { private static final PiTranslatable PI_TRANSLATABLE = new PiTranslatable() { }; - private static final PiEntity PI_ENTITY = () -> PiEntityType.TABLE_ENTRY; - private static final PiHandle PI_HANDLE = - new PiHandle(DeviceId.NONE) { + private static final PiHandle PI_HANDLE = + new PiHandle(DeviceId.NONE) { @Override public PiEntityType entityType() { return PI_ENTITY.piEntityType(); @@ -66,6 +65,17 @@ public class DistributedPiTranslationStoreTest { return String.valueOf(HANDLE_HASH); } }; + private static final PiEntity PI_ENTITY = new PiEntity() { + @Override + public PiEntityType piEntityType() { + return PiEntityType.TABLE_ENTRY; + } + + @Override + public PiHandle handle(DeviceId deviceId) { + return PI_HANDLE; + } + }; private static final PiTranslatedEntity TRANSLATED_ENTITY = new PiTranslatedEntity<>(PI_TRANSLATABLE, PI_ENTITY, PI_HANDLE); 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 03bf3500e1..5437a4ca81 100644 --- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java +++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java @@ -213,7 +213,7 @@ import org.onosproject.net.packet.PacketPriority; import org.onosproject.net.pi.model.PiActionId; import org.onosproject.net.pi.model.PiActionParamId; import org.onosproject.net.pi.model.PiActionProfileId; -import org.onosproject.net.pi.model.PiControlMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataId; import org.onosproject.net.pi.model.PiCounterId; import org.onosproject.net.pi.model.PiCounterType; import org.onosproject.net.pi.model.PiMatchFieldId; @@ -232,7 +232,7 @@ import org.onosproject.net.pi.runtime.PiActionProfileGroupId; import org.onosproject.net.pi.runtime.PiActionProfileMember; import org.onosproject.net.pi.runtime.PiActionProfileMemberHandle; import org.onosproject.net.pi.runtime.PiActionProfileMemberId; -import org.onosproject.net.pi.runtime.PiControlMetadata; +import org.onosproject.net.pi.runtime.PiPacketMetadata; import org.onosproject.net.pi.runtime.PiCounterCell; import org.onosproject.net.pi.runtime.PiCounterCellData; import org.onosproject.net.pi.runtime.PiCounterCellId; @@ -676,7 +676,7 @@ public final class KryoNamespaces { PiActionId.class, PiActionParamId.class, PiActionProfileId.class, - PiControlMetadataId.class, + PiPacketMetadataId.class, PiCounterId.class, PiCounterType.class, PiMatchFieldId.class, @@ -697,7 +697,7 @@ public final class KryoNamespaces { PiActionProfileMemberHandle.class, PiActionProfileMemberId.class, PiActionParam.class, - PiControlMetadata.class, + PiPacketMetadata.class, PiCounterCell.class, PiCounterCellData.class, PiCounterCellId.class, diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimeHandlerBehaviour.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimeHandlerBehaviour.java index 41c2b68dc7..8a9a40368f 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimeHandlerBehaviour.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimeHandlerBehaviour.java @@ -65,7 +65,7 @@ public class AbstractP4RuntimeHandlerBehaviour extends AbstractHandlerBehaviour /** * Initializes this behaviour attributes. Returns true if the operation was - * successful, false otherwise. This method assumes that the P4runtime + * successful, false otherwise. This method assumes that the P4Runtime * controller already has a client for this device and that the device has * been created in the core. * diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java index c7d76c07d9..664bd59257 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java @@ -101,7 +101,7 @@ public abstract class AbstractP4RuntimePipelineProgrammable return false; } - return client.isPipelineConfigSet(pipeconf, deviceDataBuffer); + return client.isPipelineConfigSetSync(pipeconf, deviceDataBuffer); } @Override 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 index d614d6e1b6..fca05cbb2d 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeActionGroupProgrammable.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeActionGroupProgrammable.java @@ -16,14 +16,12 @@ 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.Sets; -import com.google.common.util.concurrent.Striped; import org.onlab.util.SharedExecutors; import org.onosproject.drivers.p4runtime.mirror.P4RuntimeActionProfileGroupMirror; import org.onosproject.drivers.p4runtime.mirror.P4RuntimeActionProfileMemberMirror; +import org.onosproject.drivers.p4runtime.mirror.P4RuntimeMirror; import org.onosproject.drivers.p4runtime.mirror.TimedEntry; import org.onosproject.net.DeviceId; import org.onosproject.net.group.DefaultGroup; @@ -34,17 +32,18 @@ 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.PiActionProfileGroup; import org.onosproject.net.pi.runtime.PiActionProfileGroupHandle; import org.onosproject.net.pi.runtime.PiActionProfileMember; import org.onosproject.net.pi.runtime.PiActionProfileMemberHandle; -import org.onosproject.net.pi.runtime.PiActionProfileMemberId; +import org.onosproject.net.pi.runtime.PiEntity; +import org.onosproject.net.pi.runtime.PiHandle; 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 org.onosproject.p4runtime.api.P4RuntimeReadClient; +import org.onosproject.p4runtime.api.P4RuntimeWriteClient; import java.util.Collection; import java.util.Collections; @@ -53,14 +52,10 @@ 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 static java.util.Collections.singletonList; import static java.util.stream.Collectors.toMap; -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 java.util.stream.Collectors.toSet; /** * Implementation of GroupProgrammable to handle action profile groups in @@ -80,9 +75,6 @@ public class P4RuntimeActionGroupProgrammable private P4RuntimeActionProfileMemberMirror memberMirror; private PiGroupTranslator groupTranslator; - // Needed to synchronize operations over the same group. - private static final Striped STRIPED_LOCKS = Striped.lock(30); - @Override protected boolean setupBehaviour() { if (!super.setupBehaviour()) { @@ -134,25 +126,33 @@ public class P4RuntimeActionGroupProgrammable } // Dump groups and members from device for all action profiles. - final Set actionProfileIds = pipeconf.pipelineModel() - .actionProfiles() - .stream() - .map(PiActionProfileModel::id) - .collect(Collectors.toSet()); - final Map - groupsOnDevice = dumpAllGroupsFromDevice(actionProfileIds); + final P4RuntimeReadClient.ReadRequest request = client.read(pipeconf); + pipeconf.pipelineModel().actionProfiles() + .stream().map(PiActionProfileModel::id) + .forEach(id -> request.actionProfileGroups(id) + .actionProfileMembers(id)); + final P4RuntimeReadClient.ReadResponse response = request.submitSync(); + + if (!response.isSuccess()) { + // Error at client level. + return Collections.emptyList(); + } + + final Collection groupsOnDevice = response.all( + PiActionProfileGroup.class); final Map membersOnDevice = - dumpAllMembersFromDevice(actionProfileIds); + response.all(PiActionProfileMember.class).stream() + .collect(toMap(m -> m.handle(deviceId), m -> m)); // Sync mirrors. groupMirror.sync(deviceId, groupsOnDevice); - memberMirror.sync(deviceId, membersOnDevice); + memberMirror.sync(deviceId, membersOnDevice.values()); // Retrieve the original PD group before translation. final List result = Lists.newArrayList(); final List groupsToRemove = Lists.newArrayList(); final Set memberHandlesToKeep = Sets.newHashSet(); - for (PiActionProfileGroup piGroup : groupsOnDevice.values()) { + for (PiActionProfileGroup piGroup : groupsOnDevice) { final Group pdGroup = checkAndForgeGroupEntry(piGroup, membersOnDevice); if (pdGroup == null) { // Entry is on device but unknown to translation service or @@ -168,13 +168,24 @@ public class P4RuntimeActionGroupProgrammable } } - // Trigger clean up of inconsistent groups and members. This will update - // the mirror accordingly. + // Trigger clean up of inconsistent groups and members (if any). This + // process takes care of removing any orphan member, e.g. from a + // partial/unsuccessful group insertion. + // This will update the mirror accordingly. final Set memberHandlesToRemove = Sets.difference( membersOnDevice.keySet(), memberHandlesToKeep); - SharedExecutors.getSingleThreadExecutor().execute( - () -> cleanUpInconsistentGroupsAndMembers( - groupsToRemove, memberHandlesToRemove)); + final Set groupHandlesToRemove = groupsToRemove + .stream().map(g -> g.handle(deviceId)).collect(toSet()); + if (groupHandlesToRemove.size() + memberHandlesToRemove.size() > 0) { + log.warn("Cleaning up {} action profile groups and " + + "{} members on {}...", + groupHandlesToRemove.size(), memberHandlesToRemove.size(), deviceId); + SharedExecutors.getSingleThreadExecutor().execute( + () -> submitWriteRequestAndUpdateMirror( + client.write(pipeconf) + .delete(groupHandlesToRemove) + .delete(memberHandlesToRemove))); + } // Done. return result; @@ -182,7 +193,9 @@ public class P4RuntimeActionGroupProgrammable private Collection getGroupsFromMirror() { final Map members = - memberMirror.deviceHandleMap(deviceId); + memberMirror.getAll(deviceId).stream() + .map(TimedEntry::entry) + .collect(toMap(e -> e.handle(deviceId), e -> e)); return groupMirror.getAll(deviceId).stream() .map(TimedEntry::entry) .map(g -> checkAndForgeGroupEntry( @@ -191,60 +204,6 @@ public class P4RuntimeActionGroupProgrammable .collect(Collectors.toList()); } - private void cleanUpInconsistentGroupsAndMembers(Collection groupsToRemove, - Collection membersToRemove) { - if (!groupsToRemove.isEmpty()) { - log.warn("Found {} inconsistent action profile groups on {}, removing them...", - groupsToRemove.size(), deviceId); - groupsToRemove.forEach(piGroup -> { - log.debug(piGroup.toString()); - processPiGroup(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 - membersByActProfId = ArrayListMultimap.create(); - membersToRemove.forEach(m -> membersByActProfId.put( - m.actionProfileId(), m.memberId())); - membersByActProfId.keySet().forEach(actProfId -> { - List removedMembers = getFutureWithDeadline( - client.removeActionProfileMembers( - actProfId, membersByActProfId.get(actProfId), pipeconf), - "cleaning up action profile members", Collections.emptyList()); - // Update member mirror. - removedMembers.stream() - .map(id -> PiActionProfileMemberHandle.of(deviceId, actProfId, id)) - .forEach(memberMirror::remove); - }); - } - } - - private Map dumpAllGroupsFromDevice( - Set actProfIds) { - // TODO: implement P4Runtime client call to read all groups with one call - // Good if pipeline has multiple action profiles. - return actProfIds.stream() - .flatMap(actProfId -> getFutureWithDeadline( - client.dumpActionProfileGroups(actProfId, pipeconf), - "dumping groups", Collections.emptyList()).stream()) - .collect(toMap(g -> PiActionProfileGroupHandle.of(deviceId, g), g -> g)); - } - - private Map dumpAllMembersFromDevice( - Set actProfIds) { - // TODO: implement P4Runtime client call to read all members with one call - // Good if pipeline has multiple action profiles. - return actProfIds.stream() - .flatMap(actProfId -> getFutureWithDeadline( - client.dumpActionProfileMembers(actProfId, pipeconf), - "dumping members", Collections.emptyList()).stream()) - .collect(toMap(m -> PiActionProfileMemberHandle.of(deviceId, m), m -> m)); - } - private Group checkAndForgeGroupEntry( PiActionProfileGroup piGroupOnDevice, Map membersOnDevice) { @@ -269,7 +228,7 @@ public class P4RuntimeActionGroupProgrammable // Groups in P4Runtime contains only a reference to members. Check that // the actual member instances in the translation store are the same // found on the device. - if (!validateMembers(piGroupFromStore, membersOnDevice)) { + if (!validateGroupMembers(piGroupFromStore, membersOnDevice)) { log.warn("Group on device {} refers to members that are different " + "than those found in translation store: {}", handle); return null; @@ -282,7 +241,7 @@ public class P4RuntimeActionGroupProgrammable return addedGroup(translatedEntity.get().original(), mirrorEntry.lifeSec()); } - private boolean validateMembers( + private boolean validateGroupMembers( PiActionProfileGroup piGroupFromStore, Map membersOnDevice) { final Collection groupMembers = @@ -291,9 +250,8 @@ public class P4RuntimeActionGroupProgrammable return false; } return groupMembers.stream().allMatch( - memberFromStore -> memberFromStore.equals( - membersOnDevice.get( - PiActionProfileMemberHandle.of(deviceId, memberFromStore)))); + memberFromStore -> memberFromStore.equals(membersOnDevice.get( + memberFromStore.handle(deviceId)))); } private Group addedGroup(Group original, long life) { @@ -314,143 +272,79 @@ public class P4RuntimeActionGroupProgrammable } final Operation operation = opType.equals(GroupOperation.Type.DELETE) ? Operation.REMOVE : Operation.APPLY; - processPiGroup(piGroup, pdGroup, operation); - } - - private void processPiGroup(PiActionProfileGroup groupToApply, - Group pdGroup, - Operation operation) { - final PiActionProfileGroupHandle handle = PiActionProfileGroupHandle.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; + final PiActionProfileGroupHandle handle = piGroup.handle(deviceId); + if (writePiGroupOnDevice(piGroup, handle, operation)) { + if (operation.equals(Operation.APPLY)) { + groupTranslator.learn(handle, new PiTranslatedEntity<>( + pdGroup, piGroup, handle)); + } else { + groupTranslator.forget(handle); } - } finally { - STRIPED_LOCKS.get(handle).unlock(); } } - private boolean applyGroupWithMembersOrNothing(PiActionProfileGroup group, PiActionProfileGroupHandle handle) { - // First apply members, then group, if fails, delete members. - Collection members = extractAllMemberInstancesOrNull(group); + private boolean writePiGroupOnDevice( + PiActionProfileGroup group, + PiActionProfileGroupHandle groupHandle, + Operation operation) { + // Generate a write request to write both members and groups. Return + // true if request is successful or if there's no need to write on + // device (according to mirror state), otherwise, return false. + final Collection members = extractAllMemberInstancesOrNull(group); if (members == null) { return false; } - if (!applyAllMembersOrNothing(members)) { - return false; - } - if (!applyGroup(group, handle)) { - deleteMembers(handles(members)); - return false; - } - return true; - } - - private boolean applyGroup(PiActionProfileGroup groupToApply, PiActionProfileGroupHandle handle) { - final TimedEntry groupOnDevice = groupMirror.get(handle); - final P4RuntimeClient.WriteOperationType opType = - groupOnDevice == null ? INSERT : MODIFY; - if (opType.equals(MODIFY) && groupToApply.equals(groupOnDevice.entry())) { - // Skip writing, group is unchanged. - return true; - } - final boolean success = getFutureWithDeadline( - client.writeActionProfileGroup(groupToApply, opType, pipeconf), - "performing action profile group " + opType, false); - if (success) { - groupMirror.put(handle, groupToApply); - } - return success; - } - - private boolean deleteGroup(PiActionProfileGroup group, PiActionProfileGroupHandle handle) { - final boolean success = getFutureWithDeadline( - client.writeActionProfileGroup(group, DELETE, pipeconf), - "performing action profile group " + DELETE, false); - if (success) { - groupMirror.remove(handle); - } - // Orphan members will be removed at the next reconciliation cycle. - return success; - } - - private boolean applyAllMembersOrNothing(Collection members) { - Collection appliedMembers = applyMembers(members); - if (appliedMembers.size() == members.size()) { + final P4RuntimeWriteClient.WriteRequest request = client.write(pipeconf); + // FIXME: when operation is remove, should we remove members first? Same + // thing when modifying a group, should we first modify the group then + // remove the member? + final boolean allMembersSkipped = members.stream() + .allMatch(m -> appendEntityToWriteRequestOrSkip( + request, m.handle(deviceId), m, memberMirror, operation)); + final boolean groupSkipped = appendEntityToWriteRequestOrSkip( + request, groupHandle, group, groupMirror, operation); + if (allMembersSkipped && groupSkipped) { return true; } else { - deleteMembers(handles(appliedMembers)); - return false; + // True if all entities in the request (groups and members) where + // written successfully. + return submitWriteRequestAndUpdateMirror(request).isSuccess(); } } - private Collection applyMembers( - Collection members) { - return members.stream() - .filter(this::applyMember) - .collect(Collectors.toList()); - } - - private boolean applyMember(PiActionProfileMember memberToApply) { - // If exists, modify, otherwise insert. - final PiActionProfileMemberHandle handle = PiActionProfileMemberHandle.of( - deviceId, memberToApply); - final TimedEntry memberOnDevice = memberMirror.get(handle); - final P4RuntimeClient.WriteOperationType opType = - memberOnDevice == null ? INSERT : MODIFY; - if (opType.equals(MODIFY) && memberToApply.equals(memberOnDevice.entry())) { - // Skip writing if member is unchanged. - return true; + private boolean appendEntityToWriteRequestOrSkip( + P4RuntimeWriteClient.WriteRequest writeRequest, + H handle, + E entityToApply, + P4RuntimeMirror mirror, + Operation operation) { + // Should return true if there's no need to write entity on device, + // false if the write request is modified or an error occurs. + final TimedEntry entityOnDevice = mirror.get(handle); + switch (operation) { + case APPLY: + if (entityOnDevice == null) { + writeRequest.insert(entityToApply); + } else if (entityToApply.equals(entityOnDevice.entry())) { + // Skip writing if group is unchanged. + return true; + } else { + writeRequest.modify(entityToApply); + } + break; + case REMOVE: + if (entityOnDevice == null) { + // Skip deleting if group does not exist on device. + return true; + } else { + writeRequest.delete(handle); + } + break; + default: + log.error("Unrecognized operation {}", operation); + break; } - final boolean success = getFutureWithDeadline( - client.writeActionProfileMembers( - singletonList(memberToApply), opType, pipeconf), - "performing action profile member " + opType, false); - if (success) { - memberMirror.put(handle, memberToApply); - } - return success; - } - - private void deleteMembers(Collection handles) { - // TODO: improve by batching deletes. - handles.forEach(this::deleteMember); - } - - private void deleteMember(PiActionProfileMemberHandle handle) { - final boolean success = getFutureWithDeadline( - client.removeActionProfileMembers( - handle.actionProfileId(), - singletonList(handle.memberId()), pipeconf), - "performing action profile member " + DELETE, - Collections.emptyList()) - // Successful if the only member passed has been removed. - .size() == 1; - if (success) { - memberMirror.remove(handle); - } - } - - private Collection handles( - Collection members) { - return members.stream() - .map(m -> PiActionProfileMemberHandle.of( - deviceId, m.actionProfile(), m.id())) - .collect(Collectors.toList()); + return false; } private Collection extractAllMemberInstancesOrNull( @@ -468,6 +362,14 @@ public class P4RuntimeActionGroupProgrammable return instances; } + private P4RuntimeWriteClient.WriteResponse submitWriteRequestAndUpdateMirror( + P4RuntimeWriteClient.WriteRequest request) { + final P4RuntimeWriteClient.WriteResponse response = request.submitSync(); + groupMirror.replayWriteResponse(response); + memberMirror.replayWriteResponse(response); + return response; + } + 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 693353f1bf..f905c4c046 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 @@ -19,7 +19,6 @@ package org.onosproject.drivers.p4runtime; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.util.concurrent.Striped; import org.onlab.util.SharedExecutors; import org.onosproject.drivers.p4runtime.mirror.P4RuntimeTableMirror; import org.onosproject.drivers.p4runtime.mirror.TimedEntry; @@ -27,19 +26,24 @@ import org.onosproject.net.flow.DefaultFlowEntry; import org.onosproject.net.flow.FlowEntry; import org.onosproject.net.flow.FlowRule; import org.onosproject.net.flow.FlowRuleProgrammable; +import org.onosproject.net.pi.model.PiCounterType; import org.onosproject.net.pi.model.PiPipelineInterpreter; import org.onosproject.net.pi.model.PiPipelineModel; import org.onosproject.net.pi.model.PiTableId; -import org.onosproject.net.pi.model.PiTableModel; import org.onosproject.net.pi.runtime.PiCounterCell; import org.onosproject.net.pi.runtime.PiCounterCellData; +import org.onosproject.net.pi.runtime.PiCounterCellHandle; import org.onosproject.net.pi.runtime.PiCounterCellId; +import org.onosproject.net.pi.runtime.PiEntityType; +import org.onosproject.net.pi.runtime.PiHandle; import org.onosproject.net.pi.runtime.PiTableEntry; import org.onosproject.net.pi.runtime.PiTableEntryHandle; import org.onosproject.net.pi.service.PiFlowRuleTranslator; import org.onosproject.net.pi.service.PiTranslatedEntity; import org.onosproject.net.pi.service.PiTranslationException; -import org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType; +import org.onosproject.p4runtime.api.P4RuntimeReadClient; +import org.onosproject.p4runtime.api.P4RuntimeWriteClient; +import org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType; import java.util.Collection; import java.util.Collections; @@ -48,18 +52,14 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; -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.collect.Lists.newArrayList; import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY; import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE; import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED; -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.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.DELETE; +import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.INSERT; +import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.MODIFY; /** * Implementation of the flow rule programmable behaviour for P4Runtime. @@ -75,11 +75,6 @@ public class P4RuntimeFlowRuleProgrammable private static final String DELETE_BEFORE_UPDATE = "tableDeleteBeforeUpdate"; private static final boolean DEFAULT_DELETE_BEFORE_UPDATE = false; - // If true, we ignore re-installing rules that already exist in the - // device mirror, i.e. same match key and action. - private static final String IGNORE_SAME_ENTRY_UPDATE = "tableIgnoreSameEntryUpdate"; - private static final boolean DEFAULT_IGNORE_SAME_ENTRY_UPDATE = false; - // If true, we avoid querying the device and return what's already known by // the ONOS store. private static final String READ_FROM_MIRROR = "tableReadFromMirror"; @@ -102,9 +97,6 @@ public class P4RuntimeFlowRuleProgrammable private static final String TABLE_DEFAULT_AS_ENTRY = "tableDefaultAsEntry"; private static final boolean DEFAULT_TABLE_DEFAULT_AS_ENTRY = false; - // Needed to synchronize operations over the same table entry. - private static final Striped ENTRY_LOCKS = Striped.lock(30); - private PiPipelineModel pipelineModel; private P4RuntimeTableMirror tableMirror; private PiFlowRuleTranslator translator; @@ -136,24 +128,21 @@ public class P4RuntimeFlowRuleProgrammable final ImmutableList.Builder result = ImmutableList.builder(); final List inconsistentEntries = Lists.newArrayList(); - // Read table entries, including default ones. - final Collection deviceEntries = Stream.concat( - 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()) { + // Read table entries from device. + final Collection deviceEntries = getAllTableEntriesFromDevice(); + if (deviceEntries == null) { + // Potential error at the client level. return Collections.emptyList(); } // Synchronize mirror with the device state. - syncMirror(deviceEntries); + tableMirror.sync(deviceId, deviceEntries); + + if (deviceEntries.isEmpty()) { + // Nothing to do. + return Collections.emptyList(); + } + final Map counterCellMap = readEntryCounters(deviceEntries); // Forge flow entries with counter values. @@ -174,7 +163,7 @@ public class P4RuntimeFlowRuleProgrammable } } - if (inconsistentEntries.size() > 0) { + if (!inconsistentEntries.isEmpty()) { // Trigger clean up of inconsistent entries. SharedExecutors.getSingleThreadExecutor().execute( () -> cleanUpInconsistentEntries(inconsistentEntries)); @@ -183,32 +172,28 @@ public class P4RuntimeFlowRuleProgrammable return result.build(); } - private Stream streamEntries() { - return getFutureWithDeadline( - client.dumpAllTables(pipeconf), "dumping all tables", - Collections.emptyList()) - .stream(); - } - - private Stream streamDefaultEntries() { - // Ignore tables with constant default action. - final Set defaultTables = pipelineModel.tables() - .stream() - .filter(table -> !table.constDefaultAction().isPresent()) - .map(PiTableModel::id) - .collect(Collectors.toSet()); - return defaultTables.isEmpty() ? Stream.empty() - : getFutureWithDeadline( - client.dumpTables(defaultTables, true, pipeconf), - "dumping default table entries", - Collections.emptyList()) - .stream(); - } - - private void syncMirror(Collection entries) { - Map handleMap = Maps.newHashMap(); - entries.forEach(e -> handleMap.put(PiTableEntryHandle.of(deviceId, e), e)); - tableMirror.sync(deviceId, handleMap); + private Collection getAllTableEntriesFromDevice() { + final P4RuntimeReadClient.ReadRequest request = client.read(pipeconf); + // Read entries from all non-constant tables, including default ones. + pipelineModel.tables().stream() + .filter(t -> !t.isConstantTable()) + .forEach(t -> { + request.tableEntries(t.id()); + if (!t.constDefaultAction().isPresent()) { + request.defaultTableEntry(t.id()); + } + }); + final P4RuntimeReadClient.ReadResponse response = request.submitSync(); + if (!response.isSuccess()) { + return null; + } + return response.all(PiTableEntry.class).stream() + // 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()); } @Override @@ -223,8 +208,7 @@ public class P4RuntimeFlowRuleProgrammable private FlowEntry forgeFlowEntry(PiTableEntry entry, PiCounterCellData cellData) { - final PiTableEntryHandle handle = PiTableEntryHandle - .of(deviceId, entry); + final PiTableEntryHandle handle = entry.handle(deviceId); final Optional> translatedEntity = translator.lookup(handle); final TimedEntry timedEntry = tableMirror.get(handle); @@ -265,105 +249,157 @@ public class P4RuntimeFlowRuleProgrammable private void cleanUpInconsistentEntries(Collection piEntries) { log.warn("Found {} inconsistent table entries on {}, removing them...", piEntries.size(), deviceId); - piEntries.forEach(entry -> { - log.debug(entry.toString()); - final PiTableEntryHandle handle = PiTableEntryHandle.of(deviceId, entry); - ENTRY_LOCKS.get(handle).lock(); - try { - applyEntry(handle, entry, null, REMOVE); - } finally { - ENTRY_LOCKS.get(handle).unlock(); - } - }); + // Remove entries and update mirror. + tableMirror.replayWriteResponse( + client.write(pipeconf).entities(piEntries, DELETE).submitSync()); } private Collection processFlowRules(Collection rules, Operation driverOperation) { - if (!setupBehaviour() || rules.isEmpty()) { return Collections.emptyList(); } - - final ImmutableList.Builder result = ImmutableList.builder(); - - // TODO: send writes in bulk (e.g. all entries to insert, modify or delete). - // Instead of calling the client for each one of them. - - for (FlowRule ruleToApply : rules) { - - final PiTableEntry piEntryToApply; + // Created batched write request. + final P4RuntimeWriteClient.WriteRequest request = client.write(pipeconf); + // For each rule, translate to PI and append to write request. + final Map handleToRuleMap = Maps.newHashMap(); + final List skippedRules = Lists.newArrayList(); + for (FlowRule rule : rules) { + final PiTableEntry entry; try { - piEntryToApply = translator.translate(ruleToApply, pipeconf); + entry = translator.translate(rule, pipeconf); } catch (PiTranslationException e) { - log.warn("Unable to translate flow rule for pipeconf '{}': {} - {}", - pipeconf.id(), e.getMessage(), ruleToApply); + log.warn("Unable to translate flow rule for pipeconf '{}': {} [{}]", + pipeconf.id(), e.getMessage(), rule); // Next rule. continue; } - - final PiTableEntryHandle handle = PiTableEntryHandle - .of(deviceId, piEntryToApply); - - // Serialize operations over the same match key/table/device ID. - ENTRY_LOCKS.get(handle).lock(); - try { - if (applyEntry(handle, piEntryToApply, - ruleToApply, driverOperation)) { - result.add(ruleToApply); - } - } finally { - ENTRY_LOCKS.get(handle).unlock(); + final PiTableEntryHandle handle = entry.handle(deviceId); + handleToRuleMap.put(handle, rule); + // Append entry to batched write request (returns false), or skip (true) + if (appendEntryToWriteRequestOrSkip( + request, handle, entry, driverOperation)) { + skippedRules.add(rule); + updateTranslationStore( + driverOperation, handle, rule, entry); } } - - return result.build(); + // Submit request to server. + final P4RuntimeWriteClient.WriteResponse response = request.submitSync(); + // Update mirror. + tableMirror.replayWriteResponse(response); + // Derive successfully applied flow rule from response. + final List appliedRules = getAppliedFlowRulesAndUpdateTranslator( + response, handleToRuleMap, driverOperation); + // Return skipped and applied rules. + return ImmutableList.builder() + .addAll(skippedRules).addAll(appliedRules).build(); } - /** - * Applies the given entry to the device, and returns true if the operation - * was successful, false otherwise. - */ - private boolean applyEntry(final PiTableEntryHandle handle, - PiTableEntry piEntryToApply, - final FlowRule ruleToApply, - final Operation driverOperation) { + private List getAppliedFlowRulesAndUpdateTranslator( + P4RuntimeWriteClient.WriteResponse response, + Map handleToFlowRuleMap, + Operation driverOperation) { + // Returns a list of flow rules that were successfully written on the + // server according to the given write response and operation. + return response.success().stream() + .filter(r -> r.entityType().equals(PiEntityType.TABLE_ENTRY)) + .map(r -> { + final PiHandle handle = r.handle(); + final FlowRule rule = handleToFlowRuleMap.get(handle); + if (rule == null) { + log.error("Server returned unrecognized table entry " + + "handle in write response: {}", handle); + return null; + } + // Filter intermediate responses (e.g. P4Runtime DELETE + // during FlowRule APPLY because we are performing + // delete-before-update) + if (isUpdateTypeRelevant(r.updateType(), driverOperation)) { + updateTranslationStore( + driverOperation, (PiTableEntryHandle) handle, + rule, (PiTableEntry) r.entity()); + return rule; + } + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private boolean isUpdateTypeRelevant(UpdateType p4UpdateType, Operation driverOperation) { + switch (p4UpdateType) { + case INSERT: + case MODIFY: + if (!driverOperation.equals(APPLY)) { + return false; + } + break; + case DELETE: + if (!driverOperation.equals(REMOVE)) { + return false; + } + break; + default: + log.error("Unknown update type {}", p4UpdateType); + return false; + } + return true; + } + + private void updateTranslationStore( + Operation operation, PiTableEntryHandle handle, + FlowRule rule, PiTableEntry entry) { + if (operation.equals(APPLY)) { + translator.learn(handle, new PiTranslatedEntity<>( + rule, entry, handle)); + } else { + translator.forget(handle); + } + } + + private boolean appendEntryToWriteRequestOrSkip( + final P4RuntimeWriteClient.WriteRequest writeRequest, + final PiTableEntryHandle handle, + PiTableEntry piEntryToApply, + final Operation driverOperation) { // Depending on the driver operation, and if a matching rule exists on - // the device, decide which P4 Runtime write operation to perform for - // this entry. + // the device/mirror, decide which P4Runtime update operation to perform + // for this entry. In some cases, the entry is skipped from the write + // request but we want to return the corresponding flow rule as + // successfully written. In this case, we return true. final TimedEntry piEntryOnDevice = tableMirror.get(handle); - final WriteOperationType p4Operation; - final WriteOperationType storeOperation; + final UpdateType updateType; final boolean defaultAsEntry = driverBoolProperty( TABLE_DEFAULT_AS_ENTRY, DEFAULT_TABLE_DEFAULT_AS_ENTRY); - final boolean ignoreSameEntryUpdate = driverBoolProperty( - IGNORE_SAME_ENTRY_UPDATE, DEFAULT_IGNORE_SAME_ENTRY_UPDATE); final boolean deleteBeforeUpdate = driverBoolProperty( DELETE_BEFORE_UPDATE, DEFAULT_DELETE_BEFORE_UPDATE); + if (driverOperation == APPLY) { if (piEntryOnDevice == null) { // Entry is first-timer, INSERT or MODIFY if default action. - p4Operation = !piEntryToApply.isDefaultAction() || defaultAsEntry + updateType = !piEntryToApply.isDefaultAction() || defaultAsEntry ? INSERT : MODIFY; - storeOperation = p4Operation; } else { - if (ignoreSameEntryUpdate && - piEntryToApply.action().equals(piEntryOnDevice.entry().action())) { + if (piEntryToApply.action().equals(piEntryOnDevice.entry().action())) { + // FIXME: should we check for other attributes of the table + // entry? For example can we modify the priority? log.debug("Ignoring re-apply of existing entry: {}", piEntryToApply); - p4Operation = null; + return true; } else if (deleteBeforeUpdate && !piEntryToApply.isDefaultAction()) { - // Some devices return error when updating existing - // entries. If requested, remove entry before - // re-inserting the modified one, except the default action - // entry, that cannot be removed. - applyEntry(handle, piEntryOnDevice.entry(), null, REMOVE); - p4Operation = INSERT; + // Some devices return error when updating existing entries. + // If requested, remove entry before re-inserting the + // modified one, except the default action entry, that + // cannot be removed. + writeRequest.delete(handle); + updateType = INSERT; } else { - p4Operation = MODIFY; + updateType = MODIFY; } - storeOperation = p4Operation; } } else { + // REMOVE. if (piEntryToApply.isDefaultAction()) { // Cannot remove default action. Instead we should use the // original defined by the interpreter (if any). @@ -371,26 +407,13 @@ public class P4RuntimeFlowRuleProgrammable if (piEntryToApply == null) { return false; } - p4Operation = MODIFY; + updateType = MODIFY; } else { - p4Operation = DELETE; + updateType = DELETE; } - // Still want to delete the default entry from the mirror and - // translation store. - storeOperation = DELETE; - } - - if (p4Operation != null) { - if (writeEntry(piEntryToApply, p4Operation)) { - updateStores(handle, piEntryToApply, ruleToApply, storeOperation); - return true; - } else { - return false; - } - } else { - // If no operation, let's pretend we applied the rule to the device. - return true; } + writeRequest.entity(piEntryToApply, updateType); + return false; } private PiTableEntry getOriginalDefaultEntry(PiTableId tableId) { @@ -421,38 +444,6 @@ public class P4RuntimeFlowRuleProgrammable originalDefaultEntry.action().equals(entry.action()); } - /** - * Performs a write operation on the device. - */ - private boolean writeEntry(PiTableEntry entry, - WriteOperationType p4Operation) { - final CompletableFuture future = client.writeTableEntries( - newArrayList(entry), p4Operation, pipeconf); - // If false, errors logged by internal calls. - return getFutureWithDeadline( - future, "performing table " + p4Operation.name(), false); - } - - private void updateStores(PiTableEntryHandle handle, - PiTableEntry entry, - FlowRule rule, - WriteOperationType p4Operation) { - switch (p4Operation) { - case INSERT: - case MODIFY: - tableMirror.put(handle, entry); - translator.learn(handle, new PiTranslatedEntity<>(rule, entry, handle)); - break; - case DELETE: - tableMirror.remove(handle); - translator.forget(handle); - break; - default: - throw new IllegalArgumentException( - "Unknown operation " + p4Operation.name()); - } - } - private Map readEntryCounters( Collection tableEntries) { if (!driverBoolProperty(SUPPORT_TABLE_COUNTERS, @@ -461,22 +452,44 @@ public class P4RuntimeFlowRuleProgrammable return Collections.emptyMap(); } - if (driverBoolProperty(READ_COUNTERS_WITH_TABLE_ENTRIES, - DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES)) { - return tableEntries.stream().collect(Collectors.toMap(c -> c, PiTableEntry::counter)); - } else { - Collection cells; - Set cellIds = tableEntries.stream() - // Ignore counter for default entry. - .filter(e -> !e.isDefaultAction()) - .filter(e -> tableHasCounter(e.table())) - .map(PiCounterCellId::ofDirect) - .collect(Collectors.toSet()); - cells = getFutureWithDeadline(client.readCounterCells(cellIds, pipeconf), - "reading table counters", Collections.emptyList()); - return cells.stream() - .collect(Collectors.toMap(c -> c.cellId().tableEntry(), PiCounterCell::data)); + final Map cellDataMap = Maps.newHashMap(); + + // We expect the server to return table entries with counter data (if + // the table supports counter). Here we extract such counter data and we + // determine if there are missing counter cells (if, for example, the + // serves does not support returning counter data with table entries) + final Set missingCellHandles = tableEntries.stream() + .map(t -> { + if (t.counter() != null) { + // Counter data found in table entry. + cellDataMap.put(t, t.counter()); + return null; + } else { + return t; + } + }) + .filter(Objects::nonNull) + // Ignore for default entries and for tables without counters. + .filter(e -> !e.isDefaultAction()) + .filter(e -> tableHasCounter(e.table())) + .map(PiCounterCellId::ofDirect) + .map(id -> PiCounterCellHandle.of(deviceId, id)) + .collect(Collectors.toSet()); + // We might be sending a large read request (for thousands or more + // of counter cell handles). We request the driver to vet this + // operation via driver property. + if (!missingCellHandles.isEmpty() + && !driverBoolProperty(READ_COUNTERS_WITH_TABLE_ENTRIES, + DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES)) { + client.read(pipeconf) + .handles(missingCellHandles) + .submitSync() + .all(PiCounterCell.class).stream() + .filter(c -> c.cellId().counterType().equals(PiCounterType.DIRECT)) + .forEach(c -> cellDataMap.put(c.cellId().tableEntry(), c.data())); } + + return cellDataMap; } private boolean tableHasCounter(PiTableId tableId) { @@ -484,11 +497,6 @@ public class P4RuntimeFlowRuleProgrammable !pipelineModel.table(tableId).get().counters().isEmpty(); } - private boolean tableIsConstant(PiTableId tableId) { - return pipelineModel.table(tableId).isPresent() && - pipelineModel.table(tableId).get().isConstantTable(); - } - enum Operation { APPLY, REMOVE } diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeHandshaker.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeHandshaker.java index 15836b1b69..4d84b2aac2 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeHandshaker.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeHandshaker.java @@ -35,11 +35,12 @@ public class P4RuntimeHandshaker extends AbstractP4RuntimeHandlerBehaviour imple public CompletableFuture connect() { return CompletableFuture .supplyAsync(super::createClient) - .thenComposeAsync(client -> { + .thenApplyAsync(client -> { if (client == null) { - return CompletableFuture.completedFuture(false); + return false; } - return client.startStreamChannel(); + client.openSession(); + return true; }); } @@ -48,7 +49,7 @@ public class P4RuntimeHandshaker extends AbstractP4RuntimeHandlerBehaviour imple final P4RuntimeController controller = handler().get(P4RuntimeController.class); final DeviceId deviceId = handler().data().deviceId(); final P4RuntimeClient client = controller.getClient(deviceId); - return client != null && client.isStreamChannelOpen(); + return client != null && client.isSessionOpen(); } @Override @@ -85,12 +86,7 @@ public class P4RuntimeHandshaker extends AbstractP4RuntimeHandlerBehaviour imple @Override public void roleChanged(MastershipRole newRole) { if (setupBehaviour() && newRole.equals(MastershipRole.MASTER)) { - client.becomeMaster().thenAcceptAsync(result -> { - if (!result) { - log.error("Unable to notify mastership role {} to {}", - newRole, deviceId); - } - }); + client.runForMastership(); } } @@ -99,7 +95,7 @@ public class P4RuntimeHandshaker extends AbstractP4RuntimeHandlerBehaviour imple final P4RuntimeController controller = handler().get(P4RuntimeController.class); final DeviceId deviceId = handler().data().deviceId(); final P4RuntimeClient client = controller.getClient(deviceId); - if (client == null || !client.isStreamChannelOpen()) { + if (client == null || !client.isSessionOpen()) { return MastershipRole.NONE; } return client.isMaster() ? MastershipRole.MASTER : MastershipRole.STANDBY; diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java index f2dff80d9f..c9c436dc0a 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java @@ -31,7 +31,7 @@ import org.onosproject.net.pi.model.PiMeterId; import org.onosproject.net.pi.model.PiMeterModel; import org.onosproject.net.pi.model.PiPipelineModel; import org.onosproject.net.pi.runtime.PiMeterCellConfig; -import org.onosproject.net.pi.runtime.PiMeterHandle; +import org.onosproject.net.pi.runtime.PiMeterCellHandle; import org.onosproject.net.pi.service.PiMeterTranslator; import org.onosproject.net.pi.service.PiTranslationException; @@ -40,26 +40,23 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; -import static com.google.common.collect.Lists.newArrayList; - /** * Implementation of MeterProgrammable behaviour for P4Runtime. */ public class P4RuntimeMeterProgrammable extends AbstractP4RuntimeHandlerBehaviour implements MeterProgrammable { private static final int METER_LOCK_EXPIRE_TIME_IN_MIN = 10; - private static final LoadingCache + private static final LoadingCache ENTRY_LOCKS = CacheBuilder.newBuilder() .expireAfterAccess(METER_LOCK_EXPIRE_TIME_IN_MIN, TimeUnit.MINUTES) - .build(new CacheLoader() { + .build(new CacheLoader() { @Override - public Lock load(PiMeterHandle handle) { + public Lock load(PiMeterCellHandle handle) { return new ReentrantLock(); } }); @@ -93,7 +90,7 @@ public class P4RuntimeMeterProgrammable extends AbstractP4RuntimeHandlerBehaviou private boolean processMeterOp(MeterOperation meterOp) { if (meterOp.type() != MeterOperation.Type.MODIFY) { - log.warn("P4runtime meter operations must be MODIFY!"); + log.warn("P4Runtime meter operations must be MODIFY!"); return false; } @@ -106,11 +103,10 @@ public class P4RuntimeMeterProgrammable extends AbstractP4RuntimeHandlerBehaviou return false; } - final PiMeterHandle handle = PiMeterHandle.of(deviceId, piMeterCellConfig); + final PiMeterCellHandle handle = PiMeterCellHandle.of(deviceId, piMeterCellConfig); ENTRY_LOCKS.getUnchecked(handle).lock(); - final boolean result = getFutureWithDeadline( - client.writeMeterCells(newArrayList(piMeterCellConfig), pipeconf), - "writing meter cells", false); + final boolean result = client.write(pipeconf) + .modify(piMeterCellConfig).submitSync().isSuccess(); if (result) { meterMirror.put(handle, piMeterCellConfig); } @@ -133,13 +129,8 @@ public class P4RuntimeMeterProgrammable extends AbstractP4RuntimeHandlerBehaviou meterIds.add(mode.id()); } - try { - piMeterCellConfigs = client.readAllMeterCells(meterIds, pipeconf).get(); - } catch (InterruptedException | ExecutionException e) { - log.warn("Exception while reading meters from {}: {}", deviceId, e.toString()); - log.debug("", e); - return CompletableFuture.completedFuture(Collections.emptyList()); - } + piMeterCellConfigs = client.read(pipeconf) + .meterCells(meterIds).submitSync().all(PiMeterCellConfig.class); Collection meters = piMeterCellConfigs.stream() .map(p -> { 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 index ecdda0825e..fcb578de6f 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMulticastGroupProgrammable.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMulticastGroupProgrammable.java @@ -42,9 +42,9 @@ 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; +import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.DELETE; +import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.INSERT; +import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.MODIFY; /** * Implementation of GroupProgrammable to handle multicast groups in P4Runtime. @@ -52,6 +52,8 @@ import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.M public class P4RuntimeMulticastGroupProgrammable extends AbstractP4RuntimeHandlerBehaviour implements GroupProgrammable { + // TODO: implement reading groups from device and mirror sync. + // Needed to synchronize operations over the same group. private static final Striped STRIPED_LOCKS = Striped.lock(30); @@ -92,7 +94,7 @@ public class P4RuntimeMulticastGroupProgrammable } private Collection getMcGroups() { - // TODO: missing support for reading multicast groups is ready in PI/Stratum. + // TODO: missing support for reading multicast groups in PI/Stratum. return getMcGroupsFromMirror(); } @@ -160,17 +162,15 @@ public class P4RuntimeMulticastGroupProgrammable } } - private boolean writeMcGroupOnDevice(PiMulticastGroupEntry group, P4RuntimeClient.WriteOperationType opType) { - return getFutureWithDeadline( - client.writePreMulticastGroupEntries( - Collections.singletonList(group), opType), - "performing multicast group " + opType, false); + private boolean writeMcGroupOnDevice( + PiMulticastGroupEntry group, P4RuntimeClient.UpdateType opType) { + return client.write(pipeconf).entity(group, opType).submitSync().isSuccess(); } private boolean mcGroupApply(PiMulticastGroupEntryHandle handle, PiMulticastGroupEntry piGroup, Group pdGroup, - P4RuntimeClient.WriteOperationType opType) { + P4RuntimeClient.UpdateType opType) { switch (opType) { case DELETE: if (writeMcGroupOnDevice(piGroup, DELETE)) { diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimePacketProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimePacketProgrammable.java index 7122784b9e..73acf98c1d 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimePacketProgrammable.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimePacketProgrammable.java @@ -47,9 +47,7 @@ public class P4RuntimePacketProgrammable Collection operations = interpreter.mapOutboundPacket(packet); operations.forEach(piPacketOperation -> { log.debug("Doing PiPacketOperation {}", piPacketOperation); - getFutureWithDeadline( - client.packetOut(piPacketOperation, pipeconf), - "sending packet-out", false); + client.packetOut(piPacketOperation, pipeconf); }); } catch (PiPipelineInterpreter.PiInterpreterException e) { log.error("Unable to translate outbound packet for {} with pipeconf {}: {}", 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 e1fc1e9693..fb9451c047 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 @@ -23,10 +23,13 @@ import org.onlab.util.SharedExecutors; import org.onosproject.net.Annotations; import org.onosproject.net.DeviceId; import org.onosproject.net.pi.runtime.PiEntity; +import org.onosproject.net.pi.runtime.PiEntityType; import org.onosproject.net.pi.runtime.PiHandle; import org.onosproject.net.pi.service.PiPipeconfWatchdogEvent; import org.onosproject.net.pi.service.PiPipeconfWatchdogListener; import org.onosproject.net.pi.service.PiPipeconfWatchdogService; +import org.onosproject.p4runtime.api.P4RuntimeWriteClient; +import org.onosproject.store.serializers.KryoNamespaces; import org.onosproject.store.service.EventuallyConsistentMap; import org.onosproject.store.service.StorageService; import org.onosproject.store.service.WallClockTimestamp; @@ -66,26 +69,38 @@ public abstract class AbstractDistributedP4RuntimeMirror @Reference(cardinality = ReferenceCardinality.MANDATORY) protected PiPipeconfWatchdogService pipeconfWatchdogService; - private EventuallyConsistentMap> mirrorMap; + private EventuallyConsistentMap> mirrorMap; + private EventuallyConsistentMap annotationsMap; - private EventuallyConsistentMap annotationsMap; + private final PiEntityType entityType; private final PiPipeconfWatchdogListener pipeconfListener = new InternalPipeconfWatchdogListener(); + AbstractDistributedP4RuntimeMirror(PiEntityType entityType) { + this.entityType = entityType; + } + @Activate public void activate() { + final String mapName = "onos-p4runtime-mirror-" + + entityType.name().toLowerCase(); + final KryoNamespace serializer = KryoNamespace.newBuilder() + .register(KryoNamespaces.API) + .register(TimedEntry.class) + .build(); + mirrorMap = storageService - .>eventuallyConsistentMapBuilder() - .withName(mapName()) - .withSerializer(storeSerializer()) + .>eventuallyConsistentMapBuilder() + .withName(mapName) + .withSerializer(serializer) .withTimestampProvider((k, v) -> new WallClockTimestamp()) .build(); annotationsMap = storageService - .eventuallyConsistentMapBuilder() - .withName(mapName() + "-annotations") - .withSerializer(storeSerializer()) + .eventuallyConsistentMapBuilder() + .withName(mapName + "-annotations") + .withSerializer(serializer) .withTimestampProvider((k, v) -> new WallClockTimestamp()) .build(); @@ -93,10 +108,6 @@ public abstract class AbstractDistributedP4RuntimeMirror log.info("Started"); } - abstract String mapName(); - - abstract KryoNamespace storeSerializer(); - @Deactivate public void deactivate() { pipeconfWatchdogService.removeListener(pipeconfListener); @@ -158,9 +169,12 @@ public abstract class AbstractDistributedP4RuntimeMirror } @Override - public void sync(DeviceId deviceId, Map deviceState) { + @SuppressWarnings("unchecked") + public void sync(DeviceId deviceId, Collection entities) { checkNotNull(deviceId); - final Map localState = deviceHandleMap(deviceId); + final Map deviceState = entities.stream() + .collect(Collectors.toMap(e -> e.handle(deviceId), e -> e)); + final Map localState = deviceHandleMap(deviceId); final AtomicInteger removeCount = new AtomicInteger(0); final AtomicInteger updateCount = new AtomicInteger(0); @@ -172,7 +186,7 @@ public abstract class AbstractDistributedP4RuntimeMirror final E entryToAdd = deviceState.get(deviceHandle); log.debug("Adding mirror entry for {}: {}", deviceId, entryToAdd); - put(deviceHandle, entryToAdd); + put((H) deviceHandle, entryToAdd); addCount.incrementAndGet(); }); // Update or remove local entries. @@ -181,12 +195,12 @@ public abstract class AbstractDistributedP4RuntimeMirror final E deviceEntry = deviceState.get(localHandle); if (deviceEntry == null) { log.debug("Removing mirror entry for {}: {}", deviceId, localEntry); - remove(localHandle); + remove((H) localHandle); removeCount.incrementAndGet(); } else if (!deviceEntry.equals(localEntry)) { log.debug("Updating mirror entry for {}: {}-->{}", deviceId, localEntry, deviceEntry); - put(localHandle, deviceEntry); + put((H) localHandle, deviceEntry); updateCount.incrementAndGet(); } }); @@ -196,27 +210,48 @@ public abstract class AbstractDistributedP4RuntimeMirror } } - private Set getHandlesForDevice(DeviceId deviceId) { + private Set getHandlesForDevice(DeviceId deviceId) { return mirrorMap.keySet().stream() .filter(h -> h.deviceId().equals(deviceId)) .collect(Collectors.toSet()); } - @Override - public Map deviceHandleMap(DeviceId deviceId) { - final Map deviceMap = Maps.newHashMap(); + private Map deviceHandleMap(DeviceId deviceId) { + final Map deviceMap = Maps.newHashMap(); mirrorMap.entrySet().stream() .filter(e -> e.getKey().deviceId().equals(deviceId)) .forEach(e -> deviceMap.put(e.getKey(), e.getValue().entry())); return deviceMap; } + private void removeAll(DeviceId deviceId) { checkNotNull(deviceId); - Collection handles = getHandlesForDevice(deviceId); + @SuppressWarnings("unchecked") + Collection handles = (Collection) getHandlesForDevice(deviceId); handles.forEach(this::remove); } + @Override + @SuppressWarnings("unchecked") + public void replayWriteResponse(P4RuntimeWriteClient.WriteResponse response) { + response.success().stream() + .filter(r -> r.entityType().equals(this.entityType) && r.isSuccess()) + .forEach(r -> { + switch (r.updateType()) { + case INSERT: + case MODIFY: + put((H) r.handle(), (E) r.entity()); + break; + case DELETE: + remove((H) r.handle()); + break; + default: + log.error("Unknown update type {}", r.updateType()); + } + }); + } + public class InternalPipeconfWatchdogListener implements PiPipeconfWatchdogListener { @Override public void event(PiPipeconfWatchdogEvent event) { diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeActionProfileGroupMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeActionProfileGroupMirror.java index ef2d6bd7b6..9d5d4b10a4 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeActionProfileGroupMirror.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeActionProfileGroupMirror.java @@ -16,10 +16,9 @@ package org.onosproject.drivers.p4runtime.mirror; -import org.onlab.util.KryoNamespace; import org.onosproject.net.pi.runtime.PiActionProfileGroup; import org.onosproject.net.pi.runtime.PiActionProfileGroupHandle; -import org.onosproject.store.serializers.KryoNamespaces; +import org.onosproject.net.pi.runtime.PiEntityType; import org.osgi.service.component.annotations.Component; /** @@ -31,18 +30,7 @@ public final class DistributedP4RuntimeActionProfileGroupMirror implements P4RuntimeActionProfileGroupMirror { - private static final String DIST_MAP_NAME = "onos-p4runtime-act-prof-group-mirror"; - - @Override - String mapName() { - return DIST_MAP_NAME; - } - - @Override - KryoNamespace storeSerializer() { - return KryoNamespace.newBuilder() - .register(KryoNamespaces.API) - .register(TimedEntry.class) - .build(); + public DistributedP4RuntimeActionProfileGroupMirror() { + super(PiEntityType.ACTION_PROFILE_GROUP); } } 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 index 5b2ff21fe4..f84006b6bf 100644 --- 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 @@ -16,10 +16,9 @@ package org.onosproject.drivers.p4runtime.mirror; -import org.onlab.util.KryoNamespace; import org.onosproject.net.pi.runtime.PiActionProfileMember; import org.onosproject.net.pi.runtime.PiActionProfileMemberHandle; -import org.onosproject.store.serializers.KryoNamespaces; +import org.onosproject.net.pi.runtime.PiEntityType; import org.osgi.service.component.annotations.Component; /** @@ -31,18 +30,7 @@ public class DistributedP4RuntimeActionProfileMemberMirror 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(); + public DistributedP4RuntimeActionProfileMemberMirror() { + super(PiEntityType.ACTION_PROFILE_MEMBER); } } diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMeterMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMeterMirror.java index bf362747ac..581c83a555 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMeterMirror.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMeterMirror.java @@ -16,10 +16,9 @@ package org.onosproject.drivers.p4runtime.mirror; -import org.onlab.util.KryoNamespace; +import org.onosproject.net.pi.runtime.PiEntityType; import org.onosproject.net.pi.runtime.PiMeterCellConfig; -import org.onosproject.net.pi.runtime.PiMeterHandle; -import org.onosproject.store.serializers.KryoNamespaces; +import org.onosproject.net.pi.runtime.PiMeterCellHandle; import org.osgi.service.component.annotations.Component; /** @@ -28,21 +27,10 @@ import org.osgi.service.component.annotations.Component; @Component(immediate = true, service = P4RuntimeMeterMirror.class) public final class DistributedP4RuntimeMeterMirror extends AbstractDistributedP4RuntimeMirror - + implements P4RuntimeMeterMirror { - private static final String DIST_MAP_NAME = "onos-p4runtime-meter-mirror"; - - @Override - String mapName() { - return DIST_MAP_NAME; + public DistributedP4RuntimeMeterMirror() { + super(PiEntityType.METER_CELL_CONFIG); } - - @Override - KryoNamespace storeSerializer() { - return KryoNamespace.newBuilder() - .register(KryoNamespaces.API) - .register(TimedEntry.class) - .build(); - } -} \ No newline at end of file +} diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMulticastGroupMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMulticastGroupMirror.java index 83c23d858c..5ccaec5776 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMulticastGroupMirror.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeMulticastGroupMirror.java @@ -16,10 +16,9 @@ package org.onosproject.drivers.p4runtime.mirror; -import org.onlab.util.KryoNamespace; +import org.onosproject.net.pi.runtime.PiEntityType; import org.onosproject.net.pi.runtime.PiMulticastGroupEntry; import org.onosproject.net.pi.runtime.PiMulticastGroupEntryHandle; -import org.onosproject.store.serializers.KryoNamespaces; import org.osgi.service.component.annotations.Component; /** @@ -31,18 +30,7 @@ public final class DistributedP4RuntimeMulticastGroupMirror implements P4RuntimeMulticastGroupMirror { - private static final String DIST_MAP_NAME = "onos-p4runtime-mc-group-mirror"; - - @Override - String mapName() { - return DIST_MAP_NAME; - } - - @Override - KryoNamespace storeSerializer() { - return KryoNamespace.newBuilder() - .register(KryoNamespaces.API) - .register(TimedEntry.class) - .build(); + public DistributedP4RuntimeMulticastGroupMirror() { + super(PiEntityType.PRE_MULTICAST_GROUP_ENTRY); } } diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeTableMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeTableMirror.java index 320b3da149..4f5235ffe8 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeTableMirror.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/DistributedP4RuntimeTableMirror.java @@ -16,10 +16,9 @@ package org.onosproject.drivers.p4runtime.mirror; -import org.onlab.util.KryoNamespace; +import org.onosproject.net.pi.runtime.PiEntityType; import org.onosproject.net.pi.runtime.PiTableEntry; import org.onosproject.net.pi.runtime.PiTableEntryHandle; -import org.onosproject.store.serializers.KryoNamespaces; import org.osgi.service.component.annotations.Component; /** @@ -31,18 +30,7 @@ public final class DistributedP4RuntimeTableMirror implements P4RuntimeTableMirror { - private static final String DIST_MAP_NAME = "onos-p4runtime-table-mirror"; - - @Override - String mapName() { - return DIST_MAP_NAME; - } - - @Override - KryoNamespace storeSerializer() { - return KryoNamespace.newBuilder() - .register(KryoNamespaces.API) - .register(TimedEntry.class) - .build(); + public DistributedP4RuntimeTableMirror() { + super(PiEntityType.TABLE_ENTRY); } } diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMeterMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMeterMirror.java index 668492a385..ee88c6a9f2 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMeterMirror.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMeterMirror.java @@ -18,12 +18,12 @@ package org.onosproject.drivers.p4runtime.mirror; import com.google.common.annotations.Beta; import org.onosproject.net.pi.runtime.PiMeterCellConfig; -import org.onosproject.net.pi.runtime.PiMeterHandle; +import org.onosproject.net.pi.runtime.PiMeterCellHandle; /** * Mirror of meters installed on a P4Runtime device. */ @Beta public interface P4RuntimeMeterMirror - extends P4RuntimeMirror { + extends P4RuntimeMirror { } diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMirror.java index d62bbb848e..bee8c51e96 100644 --- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMirror.java +++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMirror.java @@ -21,9 +21,9 @@ import org.onosproject.net.Annotations; import org.onosproject.net.DeviceId; import org.onosproject.net.pi.runtime.PiEntity; import org.onosproject.net.pi.runtime.PiHandle; +import org.onosproject.p4runtime.api.P4RuntimeWriteClient; import java.util.Collection; -import java.util.Map; /** * Service to keep track of the device state for a given class of PI entities. @@ -74,15 +74,6 @@ public interface P4RuntimeMirror */ void remove(H handle); - /** - * Returns a map of handles and corresponding PI entities for the given - * device. - * - * @param deviceId device ID - * @return map of handles and corresponding PI entities - */ - Map deviceHandleMap(DeviceId deviceId); - /** * Stores the given annotations associating it to the given handle. * @@ -101,10 +92,18 @@ public interface P4RuntimeMirror Annotations annotations(H handle); /** - * Synchronizes the state of the given device ID with the given handle map. - * - * @param deviceId device ID - * @param handleMap handle map + * Synchronizes the state of the given device ID with the given collection + * of PI entities. + * @param deviceId device ID + * @param entities collection of PI entities */ - void sync(DeviceId deviceId, Map handleMap); + void sync(DeviceId deviceId, Collection entities); + + /** + * Uses the given P4Runtime write response to update the state of this + * mirror. + * + * @param response P4Runtime write response + */ + void replayWriteResponse(P4RuntimeWriteClient.WriteResponse response); } diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java index c36a77ecaf..667897b54c 100644 --- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java +++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java @@ -19,7 +19,7 @@ package org.onosproject.pipelines.basic; import org.onosproject.net.pi.model.PiActionId; import org.onosproject.net.pi.model.PiActionParamId; import org.onosproject.net.pi.model.PiActionProfileId; -import org.onosproject.net.pi.model.PiControlMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataId; import org.onosproject.net.pi.model.PiCounterId; import org.onosproject.net.pi.model.PiMatchFieldId; import org.onosproject.net.pi.model.PiTableId; @@ -91,10 +91,10 @@ public final class BasicConstants { public static final PiActionProfileId INGRESS_WCMP_CONTROL_WCMP_SELECTOR = PiActionProfileId.of("ingress.wcmp_control.wcmp_selector"); // Packet Metadata IDs - public static final PiControlMetadataId PADDING = - PiControlMetadataId.of("_padding"); - public static final PiControlMetadataId INGRESS_PORT = - PiControlMetadataId.of("ingress_port"); - public static final PiControlMetadataId EGRESS_PORT = - PiControlMetadataId.of("egress_port"); + public static final PiPacketMetadataId PADDING = + PiPacketMetadataId.of("_padding"); + public static final PiPacketMetadataId INGRESS_PORT = + PiPacketMetadataId.of("ingress_port"); + public static final PiPacketMetadataId EGRESS_PORT = + PiPacketMetadataId.of("egress_port"); } diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java index 56a95fa3fe..eb49819cf7 100644 --- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java +++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java @@ -22,6 +22,7 @@ import org.onlab.packet.DeserializationException; import org.onlab.packet.Ethernet; import org.onlab.util.ImmutableByteSequence; import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; import org.onosproject.net.Port; import org.onosproject.net.PortNumber; import org.onosproject.net.device.DeviceService; @@ -38,7 +39,7 @@ import org.onosproject.net.pi.model.PiPipelineInterpreter; import org.onosproject.net.pi.model.PiTableId; import org.onosproject.net.pi.runtime.PiAction; import org.onosproject.net.pi.runtime.PiActionParam; -import org.onosproject.net.pi.runtime.PiControlMetadata; +import org.onosproject.net.pi.runtime.PiPacketMetadata; import org.onosproject.net.pi.runtime.PiPacketOperation; import java.nio.ByteBuffer; @@ -173,7 +174,7 @@ public class BasicInterpreterImpl extends AbstractHandlerBehaviour } @Override - public InboundPacket mapInboundPacket(PiPacketOperation packetIn) + public InboundPacket mapInboundPacket(PiPacketOperation packetIn, DeviceId deviceId) throws PiInterpreterException { // Assuming that the packet is ethernet, which is fine since basic.p4 // can deparse only ethernet packets. @@ -186,37 +187,36 @@ public class BasicInterpreterImpl extends AbstractHandlerBehaviour } // Returns the ingress port packet metadata. - Optional packetMetadata = packetIn.metadatas() + Optional packetMetadata = packetIn.metadatas() .stream().filter(m -> m.id().equals(INGRESS_PORT)) .findFirst(); if (packetMetadata.isPresent()) { ImmutableByteSequence portByteSequence = packetMetadata.get().value(); short s = portByteSequence.asReadOnlyBuffer().getShort(); - ConnectPoint receivedFrom = new ConnectPoint(packetIn.deviceId(), PortNumber.portNumber(s)); + ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s)); ByteBuffer rawData = ByteBuffer.wrap(packetIn.data().asArray()); return new DefaultInboundPacket(receivedFrom, ethPkt, rawData); } else { throw new PiInterpreterException(format( "Missing metadata '%s' in packet-in received from '%s': %s", - INGRESS_PORT, packetIn.deviceId(), packetIn)); + INGRESS_PORT, deviceId, packetIn)); } } private PiPacketOperation createPiPacketOperation(ByteBuffer data, long portNumber) throws PiInterpreterException { - PiControlMetadata metadata = createPacketMetadata(portNumber); + PiPacketMetadata metadata = createPacketMetadata(portNumber); return PiPacketOperation.builder() - .forDevice(this.data().deviceId()) .withType(PACKET_OUT) .withData(copyFrom(data)) .withMetadatas(ImmutableList.of(metadata)) .build(); } - private PiControlMetadata createPacketMetadata(long portNumber) throws PiInterpreterException { + private PiPacketMetadata createPacketMetadata(long portNumber) throws PiInterpreterException { try { - return PiControlMetadata.builder() + return PiPacketMetadata.builder() .withId(EGRESS_PORT) .withValue(copyFrom(portNumber).fit(PORT_BITWIDTH)) .build(); diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java index 0057293ebc..f7cb811588 100644 --- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java +++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java @@ -29,6 +29,7 @@ import org.onosproject.net.driver.AbstractHandlerBehaviour; import org.onosproject.net.pi.model.PiCounterId; import org.onosproject.net.pi.model.PiPipeconf; import org.onosproject.net.pi.runtime.PiCounterCell; +import org.onosproject.net.pi.runtime.PiCounterCellHandle; import org.onosproject.net.pi.runtime.PiCounterCellId; import org.onosproject.net.pi.service.PiPipeconfService; import org.onosproject.p4runtime.api.P4RuntimeClient; @@ -40,7 +41,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.onosproject.net.pi.model.PiCounterType.INDIRECT; @@ -111,15 +111,14 @@ public class PortStatisticsDiscoveryImpl extends AbstractHandlerBehaviour implem counterCellIds.add(PiCounterCellId.ofIndirect(ingressCounterId(), p)); counterCellIds.add(PiCounterCellId.ofIndirect(egressCounterId(), p)); }); + Set counterCellHandles = counterCellIds.stream() + .map(id -> PiCounterCellHandle.of(deviceId, id)) + .collect(Collectors.toSet()); - Collection counterEntryResponse; - try { - counterEntryResponse = client.readCounterCells(counterCellIds, pipeconf).get(); - } catch (InterruptedException | ExecutionException e) { - log.warn("Exception while reading port counters from {}: {}", deviceId, e.toString()); - log.debug("", e); - return Collections.emptyList(); - } + // Query the device. + Collection counterEntryResponse = client.read(pipeconf) + .handles(counterCellHandles).submitSync() + .all(PiCounterCell.class); counterEntryResponse.forEach(counterCell -> { if (counterCell.cellId().counterType() != INDIRECT) { diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java index 9ecc65ffe4..882a624c60 100644 --- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java +++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java @@ -19,7 +19,7 @@ package org.onosproject.pipelines.fabric; import org.onosproject.net.pi.model.PiActionId; import org.onosproject.net.pi.model.PiActionParamId; import org.onosproject.net.pi.model.PiActionProfileId; -import org.onosproject.net.pi.model.PiControlMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataId; import org.onosproject.net.pi.model.PiCounterId; import org.onosproject.net.pi.model.PiMatchFieldId; import org.onosproject.net.pi.model.PiTableId; @@ -254,8 +254,8 @@ public final class FabricConstants { public static final PiActionProfileId FABRIC_INGRESS_NEXT_HASHED_SELECTOR = PiActionProfileId.of("FabricIngress.next.hashed_selector"); // Packet Metadata IDs - public static final PiControlMetadataId INGRESS_PORT = - PiControlMetadataId.of("ingress_port"); - public static final PiControlMetadataId EGRESS_PORT = - PiControlMetadataId.of("egress_port"); + public static final PiPacketMetadataId INGRESS_PORT = + PiPacketMetadataId.of("ingress_port"); + public static final PiPacketMetadataId EGRESS_PORT = + PiPacketMetadataId.of("egress_port"); } diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java index 580d6f76f7..e5234729c7 100644 --- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java +++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java @@ -37,7 +37,7 @@ import org.onosproject.net.pi.model.PiMatchFieldId; import org.onosproject.net.pi.model.PiPipelineInterpreter; import org.onosproject.net.pi.model.PiTableId; import org.onosproject.net.pi.runtime.PiAction; -import org.onosproject.net.pi.runtime.PiControlMetadata; +import org.onosproject.net.pi.runtime.PiPacketMetadata; import org.onosproject.net.pi.runtime.PiPacketOperation; import java.nio.ByteBuffer; @@ -172,19 +172,18 @@ public class FabricInterpreter extends AbstractFabricHandlerBehavior private PiPacketOperation createPiPacketOperation( DeviceId deviceId, ByteBuffer data, long portNumber) throws PiInterpreterException { - PiControlMetadata metadata = createPacketMetadata(portNumber); + PiPacketMetadata metadata = createPacketMetadata(portNumber); return PiPacketOperation.builder() - .forDevice(deviceId) .withType(PACKET_OUT) .withData(copyFrom(data)) .withMetadatas(ImmutableList.of(metadata)) .build(); } - private PiControlMetadata createPacketMetadata(long portNumber) + private PiPacketMetadata createPacketMetadata(long portNumber) throws PiInterpreterException { try { - return PiControlMetadata.builder() + return PiPacketMetadata.builder() .withId(FabricConstants.EGRESS_PORT) .withValue(copyFrom(portNumber).fit(PORT_BITWIDTH)) .build(); @@ -233,10 +232,9 @@ public class FabricInterpreter extends AbstractFabricHandlerBehavior } @Override - public InboundPacket mapInboundPacket(PiPacketOperation packetIn) throws PiInterpreterException { + public InboundPacket mapInboundPacket(PiPacketOperation packetIn, DeviceId deviceId) throws PiInterpreterException { // Assuming that the packet is ethernet, which is fine since fabric.p4 // can deparse only ethernet packets. - DeviceId deviceId = packetIn.deviceId(); Ethernet ethPkt; try { ethPkt = Ethernet.deserializer().deserialize(packetIn.data().asArray(), 0, @@ -246,7 +244,7 @@ public class FabricInterpreter extends AbstractFabricHandlerBehavior } // Returns the ingress port packet metadata. - Optional packetMetadata = packetIn.metadatas() + Optional packetMetadata = packetIn.metadatas() .stream().filter(m -> m.id().equals(FabricConstants.INGRESS_PORT)) .findFirst(); diff --git a/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClient.java b/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClient.java index 05e2978d0f..0b21ca2240 100644 --- a/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClient.java +++ b/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClient.java @@ -18,7 +18,6 @@ package org.onosproject.grpc.ctl; import io.grpc.Context; import io.grpc.StatusRuntimeException; -import org.onlab.util.SharedExecutors; import org.onosproject.grpc.api.GrpcClient; import org.onosproject.grpc.api.GrpcClientKey; import org.onosproject.net.DeviceId; @@ -33,12 +32,12 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; +import static com.google.common.base.Preconditions.checkNotNull; import static org.onlab.util.Tools.groupedThreads; import static org.slf4j.LoggerFactory.getLogger; /** * Abstract client for gRPC service. - * */ public abstract class AbstractGrpcClient implements GrpcClient { @@ -57,6 +56,7 @@ public abstract class AbstractGrpcClient implements GrpcClient { protected final DeviceId deviceId; protected AbstractGrpcClient(GrpcClientKey clientKey) { + checkNotNull(clientKey); this.deviceId = clientKey.deviceId(); this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE, groupedThreads( "onos-grpc-" + clientKey.serviceName() + "-client-" + deviceId.toString(), "%d")); @@ -65,12 +65,16 @@ public abstract class AbstractGrpcClient implements GrpcClient { @Override public CompletableFuture shutdown() { - return supplyWithExecutor(this::doShutdown, "shutdown", - SharedExecutors.getPoolThreadExecutor()); + if (cancellableContext.isCancelled()) { + log.warn("Context is already cancelled, " + + "ignoring request to shutdown for {}...", deviceId); + return CompletableFuture.completedFuture(null); + } + return CompletableFuture.supplyAsync(this::doShutdown); } protected Void doShutdown() { - log.debug("Shutting down client for {}...", deviceId); + log.warn("Shutting down client for {}...", deviceId); cancellableContext.cancel(new InterruptedException( "Requested client shutdown")); this.executorService.shutdownNow(); @@ -83,17 +87,41 @@ public abstract class AbstractGrpcClient implements GrpcClient { return null; } + /** + * Executes the given task in the cancellable context of this client. + * + * @param task task + * @throws IllegalStateException if context has been cancelled + */ + protected void runInCancellableContext(Runnable task) { + if (this.cancellableContext.isCancelled()) { + throw new IllegalStateException( + "Context is cancelled (client has been shut down)"); + } + this.cancellableContext.run(task); + } + + /** + * Returns the context associated with this client. + * + * @return context + */ + protected Context.CancellableContext context() { + return cancellableContext; + } + /** * Equivalent of supplyWithExecutor using the gRPC context executor of this * client, such that if the context is cancelled (e.g. client shutdown) the * RPC is automatically cancelled. * - * @param return type of supplier - * @param supplier the supplier to be executed + * @param return type of supplier + * @param supplier the supplier to be executed * @param opDescription the description of this supplier * @return CompletableFuture includes the result of supplier + * @throws IllegalStateException if client has been shut down */ - protected CompletableFuture supplyInContext( + protected CompletableFuture supplyInContext( Supplier supplier, String opDescription) { return supplyWithExecutor(supplier, opDescription, contextExecutor); } @@ -102,37 +130,41 @@ public abstract class AbstractGrpcClient implements GrpcClient { * Submits a task for async execution via the given executor. All tasks * submitted with this method will be executed sequentially. * - * @param return type of supplier - * @param supplier the supplier to be executed + * @param return type of supplier + * @param supplier the supplier to be executed * @param opDescription the description of this supplier - * @param executor the executor to execute this supplier + * @param executor the executor to execute this supplier * @return CompletableFuture includes the result of supplier + * @throws IllegalStateException if client has been shut down */ private CompletableFuture supplyWithExecutor( Supplier supplier, String opDescription, Executor executor) { + if (this.cancellableContext.isCancelled()) { + throw new IllegalStateException("Client has been shut down"); + } return CompletableFuture.supplyAsync(() -> { // TODO: explore a more relaxed locking strategy. try { if (!requestLock.tryLock(LOCK_TIMEOUT, TimeUnit.SECONDS)) { log.error("LOCK TIMEOUT! This is likely a deadlock, " - + "please debug (executing {})", - opDescription); + + "please debug (executing {})", + opDescription); throw new IllegalThreadStateException("Lock timeout"); } } catch (InterruptedException e) { log.warn("Thread interrupted while waiting for lock (executing {})", - opDescription); + opDescription); throw new IllegalStateException(e); } try { return supplier.get(); } catch (StatusRuntimeException ex) { log.warn("Unable to execute {} on {}: {}", - opDescription, deviceId, ex.toString()); + opDescription, deviceId, ex.toString()); throw ex; } catch (Throwable ex) { log.error("Exception in client of {}, executing {}", - deviceId, opDescription, ex); + deviceId, opDescription, ex); throw ex; } finally { requestLock.unlock(); 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 ff00a1a674..31db294d9a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2017-present Open Networking Foundation + * Copyright 2019-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. @@ -16,284 +16,13 @@ package org.onosproject.p4runtime.api; -import com.google.common.annotations.Beta; import org.onosproject.grpc.api.GrpcClient; -import org.onosproject.net.pi.model.PiActionProfileId; -import org.onosproject.net.pi.model.PiCounterId; -import org.onosproject.net.pi.model.PiMeterId; -import org.onosproject.net.pi.model.PiPipeconf; -import org.onosproject.net.pi.model.PiTableId; -import org.onosproject.net.pi.runtime.PiActionProfileGroup; -import org.onosproject.net.pi.runtime.PiActionProfileMember; -import org.onosproject.net.pi.runtime.PiActionProfileMemberId; -import org.onosproject.net.pi.runtime.PiCounterCell; -import org.onosproject.net.pi.runtime.PiCounterCellId; -import org.onosproject.net.pi.runtime.PiMeterCellConfig; -import org.onosproject.net.pi.runtime.PiMeterCellId; -import org.onosproject.net.pi.runtime.PiMulticastGroupEntry; -import org.onosproject.net.pi.runtime.PiPacketOperation; -import org.onosproject.net.pi.runtime.PiTableEntry; - -import java.nio.ByteBuffer; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; /** - * Client to control a P4Runtime device. + * Root interface exposing all P4Runtime client capabilities. */ -@Beta -public interface P4RuntimeClient extends GrpcClient { +public interface P4RuntimeClient + extends GrpcClient, P4RuntimePipelineConfigClient, P4RuntimeStreamClient, + P4RuntimeWriteClient, P4RuntimeReadClient { - /** - * Type of write operation. - */ - enum WriteOperationType { - UNSPECIFIED, - INSERT, - MODIFY, - DELETE - } - - /** - * Starts the Stream RPC with the device. - * - * @return completable future containing true if the operation was - * successful, false otherwise. - */ - CompletableFuture startStreamChannel(); - - /** - * Returns true if the stream RPC is active, false otherwise. - * - * @return boolean - */ - boolean isStreamChannelOpen(); - - /** - * Sends a master arbitration update to the device with a new election ID - * that is guaranteed to be the highest value between all clients. - * - * @return completable future containing true if the operation was - * successful; false otherwise - */ - CompletableFuture becomeMaster(); - - /** - * Returns true if this client is master for the device, false otherwise. - * - * @return boolean - */ - boolean isMaster(); - - /** - * Sets the device pipeline according to the given pipeconf, and for the - * given byte buffer representing the target-specific data to be used in the - * P4Runtime's SetPipelineConfig message. This method should be called - * before any other method of this client. - * - * @param pipeconf pipeconf - * @param deviceData target-specific data - * @return a completable future of a boolean, true if the operations was - * successful, false otherwise. - */ - CompletableFuture setPipelineConfig( - PiPipeconf pipeconf, ByteBuffer deviceData); - - /** - * Returns true if the device has the given pipeconf set, false otherwise. - * Equality is based on the P4Info extension of the pipeconf as well as the - * given device data byte buffer. - *

- * This method is expected to return {@code true} if invoked after calling - * {@link #setPipelineConfig(PiPipeconf, ByteBuffer)} with the same - * parameters. - * - * @param pipeconf pipeconf - * @param deviceData target-specific data - * @return boolean - */ - boolean isPipelineConfigSet(PiPipeconf pipeconf, ByteBuffer deviceData); - - /** - * Performs the given write operation for the given table entries and - * pipeconf. - * - * @param entries table entries - * @param opType operation type - * @param pipeconf pipeconf currently deployed on the device - * @return true if the operation was successful, false otherwise. - */ - CompletableFuture writeTableEntries( - List entries, WriteOperationType opType, - PiPipeconf pipeconf); - - /** - * Dumps all entries currently installed in the given tables, for the given - * pipeconf. If defaultEntries is set to true only the default action - * 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 list of table entries - */ - CompletableFuture> dumpTables( - Set 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 list of table entries - */ - CompletableFuture> dumpAllTables(PiPipeconf pipeconf); - - /** - * Executes a packet-out operation for the given pipeconf. - * - * @param packet packet-out operation to be performed by the device - * @param pipeconf pipeconf currently deployed on the device - * @return a completable future of a boolean, true if the operations was - * successful, false otherwise. - */ - CompletableFuture packetOut( - PiPacketOperation packet, PiPipeconf pipeconf); - - /** - * Returns the value of all counter cells for the given set of counter - * identifiers and pipeconf. - * - * @param counterIds counter identifiers - * @param pipeconf pipeconf - * @return list of counter data - */ - CompletableFuture> readAllCounterCells( - Set counterIds, PiPipeconf 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 list of counter data - */ - CompletableFuture> readCounterCells( - Set cellIds, PiPipeconf pipeconf); - - /** - * Performs the given write operation for the given action profile members - * and pipeconf. - * - * @param members action profile 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 writeActionProfileMembers( - List members, - WriteOperationType opType, PiPipeconf pipeconf); - - /** - * Performs the given write operation for the given action profile group and - * pipeconf. - * - * @param group the action profile group - * @param opType write operation type - * @param pipeconf the pipeconf currently deployed on the device - * @return true if the operation was successful, false otherwise - */ - CompletableFuture writeActionProfileGroup( - PiActionProfileGroup group, - WriteOperationType opType, - PiPipeconf pipeconf); - - /** - * Dumps all groups for a given action profile. - * - * @param actionProfileId the action profile id - * @param pipeconf the pipeconf currently deployed on the device - * @return completable future of a list of groups - */ - CompletableFuture> dumpActionProfileGroups( - PiActionProfileId actionProfileId, PiPipeconf pipeconf); - - /** - * Dumps all members for a given action profile. - * - * @param actionProfileId action profile ID - * @param pipeconf pipeconf - * @return future of list of action profile member ID - */ - CompletableFuture> dumpActionProfileMembers( - 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> removeActionProfileMembers( - PiActionProfileId actionProfileId, - List 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 list of meter configurations - */ - CompletableFuture> readAllMeterCells( - Set meterIds, PiPipeconf 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 list of meter configrations - */ - CompletableFuture> readMeterCells( - Set cellIds, PiPipeconf pipeconf); - - /** - * Performs a write operation for the given meter configurations and - * pipeconf. - * - * @param cellConfigs meter cell configurations - * @param pipeconf pipeconf currently deployed on the device - * @return true if the operation was successful, false otherwise. - */ - CompletableFuture writeMeterCells( - List cellConfigs, PiPipeconf pipeconf); - - /** - * Performs the given write operation for the given PI multicast groups - * entries. - * - * @param entries multicast group entries - * @param opType write operation type - * @return true if the operation was successful, false otherwise - */ - CompletableFuture writePreMulticastGroupEntries( - List entries, - WriteOperationType opType); - - /** - * Returns all multicast groups on device. - * - * @return multicast groups - */ - CompletableFuture> readAllMulticastGroupEntries(); } diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimePipelineConfigClient.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimePipelineConfigClient.java new file mode 100644 index 0000000000..34dac66b7a --- /dev/null +++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimePipelineConfigClient.java @@ -0,0 +1,92 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.api; + +import com.google.common.util.concurrent.Futures; +import org.onosproject.net.pi.model.PiPipeconf; + +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * P4Runtime client interface for the pipeline configuration-related RPCs. + */ +public interface P4RuntimePipelineConfigClient { + + /** + * Uploads and commit a pipeline configuration to the server using the + * {@link PiPipeconf.ExtensionType#P4_INFO_TEXT} extension of the given + * pipeconf, and the given byte buffer as the target-specific data ("P4 + * blob") to be used in the P4Runtime's {@code SetPipelineConfig} message. + * Returns true if the operations was successful, false otherwise. + * + * @param pipeconf pipeconf + * @param deviceData target-specific data + * @return completable future, true if the operations was successful, false + * otherwise. + */ + CompletableFuture setPipelineConfig( + PiPipeconf pipeconf, ByteBuffer deviceData); + + /** + * Same as {@link #setPipelineConfig(PiPipeconf, ByteBuffer)}, but blocks + * execution. + * + * @param pipeconf pipeconf + * @param deviceData target-specific data + * @return true if the operations was successful, false otherwise. + */ + default boolean setPipelineConfigSync( + PiPipeconf pipeconf, ByteBuffer deviceData) { + checkNotNull(pipeconf); + checkNotNull(deviceData); + return Futures.getUnchecked(setPipelineConfig(pipeconf, deviceData)); + } + + /** + * Returns true if the device has the given pipeconf set, false otherwise. + * If possible, equality should be based on {@link PiPipeconf#fingerprint()}, + * otherwise, the implementation can request the server to send the whole + * P4Info and target-specific data for comparison. + *

+ * This method is expected to return {@code true} if invoked after calling + * {@link #setPipelineConfig(PiPipeconf, ByteBuffer)} with the same + * parameters. + * + * @param pipeconf pipeconf + * @param deviceData target-specific data + * @return completable future, true if the device has the given pipeconf + * set, false otherwise. + */ + CompletableFuture isPipelineConfigSet( + PiPipeconf pipeconf, ByteBuffer deviceData); + + /** + * Same as {@link #isPipelineConfigSet(PiPipeconf, ByteBuffer)} but blocks + * execution. + * + * @param pipeconf pipeconf + * @param deviceData target-specific data + * @return true if the device has the given pipeconf set, false otherwise. + */ + default boolean isPipelineConfigSetSync( + PiPipeconf pipeconf, ByteBuffer deviceData) { + return Futures.getUnchecked(isPipelineConfigSet(pipeconf, deviceData)); + } +} diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeReadClient.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeReadClient.java new file mode 100644 index 0000000000..9a47398961 --- /dev/null +++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeReadClient.java @@ -0,0 +1,275 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.api; + +import org.onosproject.net.pi.model.PiActionProfileId; +import org.onosproject.net.pi.model.PiCounterId; +import org.onosproject.net.pi.model.PiMeterId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.model.PiTableId; +import org.onosproject.net.pi.runtime.PiEntity; +import org.onosproject.net.pi.runtime.PiHandle; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +/** + * P4Runtime client interface for the Read RPC that allows reading multiple + * entities with one request. + */ +public interface P4RuntimeReadClient { + + /** + * Returns a new {@link ReadRequest} instance that can bed used to build a + * batched read request, for the given pipeconf. + * + * @param pipeconf pipeconf + * @return new read request + */ + ReadRequest read(PiPipeconf pipeconf); + + /** + * Abstraction of a P4Runtime read request that follows the builder pattern. + * Multiple entities can be added to the same request before submitting it. + */ + interface ReadRequest { + + /** + * Requests to read one entity identified by the given handle. + * + * @param handle handle + * @return this + */ + ReadRequest handle(PiHandle handle); + + /** + * Requests to read multiple entities identified by the given handles. + * + * @param handles iterable of handles + * @return this + */ + ReadRequest handles(Iterable handles); + + /** + * Requests to read all table entries from the given table ID. + * + * @param tableId table ID + * @return this + */ + ReadRequest tableEntries(PiTableId tableId); + + /** + * Requests to read all table entries from the given table IDs. + * + * @param tableIds table IDs + * @return this + */ + ReadRequest tableEntries(Iterable tableIds); + + /** + * Requests to read the default table entry from the given table. + * + * @param tableId table ID + * @return this + */ + ReadRequest defaultTableEntry(PiTableId tableId); + + /** + * Requests to read the default table entry from the given tables. + * + * @param tableIds table IDs + * @return this + */ + ReadRequest defaultTableEntry(Iterable tableIds); + + /** + * Requests to read all action profile groups from the given action + * profile. + * + * @param actionProfileId action profile ID + * @return this + */ + ReadRequest actionProfileGroups(PiActionProfileId actionProfileId); + + /** + * Requests to read all action profile groups from the given action + * profiles. + * + * @param actionProfileIds action profile IDs + * @return this + */ + ReadRequest actionProfileGroups(Iterable actionProfileIds); + + /** + * Requests to read all action profile members from the given action + * profile. + * + * @param actionProfileId action profile ID + * @return this + */ + ReadRequest actionProfileMembers(PiActionProfileId actionProfileId); + + /** + * Requests to read all action profile members from the given action + * profiles. + * + * @param actionProfileIds action profile IDs + * @return this + */ + ReadRequest actionProfileMembers(Iterable actionProfileIds); + + /** + * Requests to read all counter cells from the given counter. + * + * @param counterId counter ID + * @return this + */ + ReadRequest counterCells(PiCounterId counterId); + + /** + * Requests to read all counter cells from the given counters. + * + * @param counterIds counter IDs + * @return this + */ + ReadRequest counterCells(Iterable counterIds); + + /** + * Requests to read all direct counter cells from the given table. + * + * @param tableId table ID + * @return this + */ + ReadRequest directCounterCells(PiTableId tableId); + + /** + * Requests to read all direct counter cells from the given tables. + * + * @param tableIds table IDs + * @return this + */ + ReadRequest directCounterCells(Iterable tableIds); + + /** + * Requests to read all meter cell configs from the given meter ID. + * + * @param meterId meter ID + * @return this + */ + ReadRequest meterCells(PiMeterId meterId); + + /** + * Requests to read all meter cell configs from the given meter IDs. + * + * @param meterIds meter IDs + * @return this + */ + ReadRequest meterCells(Iterable meterIds); + + /** + * Requests to read all direct meter cell configs from the given table. + * + * @param tableId table ID + * @return this + */ + ReadRequest directMeterCells(PiTableId tableId); + + /** + * Requests to read all direct meter cell configs from the given + * tables. + * + * @param tableIds table IDs + * @return this + */ + ReadRequest directMeterCells(Iterable tableIds); + + /** + * Submits the read request and returns a read response wrapped in a + * completable future. The future is completed once all entities have + * been received by the P4Runtime client. + * + * @return completable future of a read response + */ + CompletableFuture submit(); + + /** + * Similar to {@link #submit()}, but blocks until the operation is + * completed, after which, it returns a read response. + * + * @return read response + */ + ReadResponse submitSync(); + + //TODO: implement per-entity asynchronous reads. This would allow a user + // of this client to process read entities as they arrive, instead of + // waiting for the client to receive them all. Java 9 Reactive Streams + // seems a good way of doing it. + } + + /** + * Response to a P4Runtime read request. + */ + interface ReadResponse { + + /** + * Returns true if the request was successful with no errors, otherwise + * returns false. In case of errors, further details can be obtained + * with {@link #explanation()} and {@link #throwable()}. + * + * @return true if the request was successful with no errors, false + * otherwise + */ + boolean isSuccess(); + + /** + * Returns a collection of all PI entities returned by the server. + * + * @return collection of all PI entities returned by the server + */ + Collection all(); + + /** + * Returns a collection of all PI entities of a given class returned by + * the server. + * + * @param clazz PI entity class + * @param PI entity class + * @return collection of all PI entities returned by the server for the + * given PI entity class + */ + Collection all(Class clazz); + + /** + * If the read request was not successful, this method returns a message + * explaining the error occurred. Returns an empty string if such + * message is not available, or {@code null} if no errors occurred. + * + * @return error explanation or empty string or null + */ + String explanation(); + + /** + * If the read request was not successful, this method returns the + * internal throwable instance associated with the error (e.g. a {@link + * io.grpc.StatusRuntimeException} instance). Returns null if such + * throwable instance is not available or if no errors occurred. + * + * @return throwable instance + */ + Throwable throwable(); + } +} diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeStreamClient.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeStreamClient.java new file mode 100644 index 0000000000..1317e29a01 --- /dev/null +++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeStreamClient.java @@ -0,0 +1,85 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.api; + +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiPacketOperation; + +/** + * P4Runtime client interface for the StreamChannel RPC. It allows management of + * the P4Runtime session (open/close, mastership arbitration) as well as sending + * packet-outs. All messages received from the server via the stream channel, + * such as master arbitration updates, or packet-ins, are handled by the + * P4RuntimeController. Anyone interested in those messages should register a + * listener with the latter. + */ +public interface P4RuntimeStreamClient { + + /** + * Opens a session to the server by starting the Stream RPC and sending a + * mastership arbitration update message with an election ID that is + * expected to be unique among all available clients. If a client has been + * requested to become master via {@link #runForMastership()}, then this + * method should pick an election ID that is lower than the one currently + * associated with the master client. + *

+ * If the server acknowledges the session to this client as open, the {@link + * P4RuntimeController} is expected to generate a {@link + * org.onosproject.net.device.DeviceAgentEvent} with type {@link + * org.onosproject.net.device.DeviceAgentEvent.Type#CHANNEL_OPEN}. + */ + void openSession(); + + /** + * Returns true if the Stream RPC is active and the P4Runtime session is + * open, false otherwise. + * + * @return boolean + */ + boolean isSessionOpen(); + + /** + * Closes the session to the server by terminating the Stream RPC. + */ + void closeSession(); + + /** + * Sends a master arbitration update to the device with a new election ID + * that is expected to be the highest one between all clients. + *

+ * If the server acknowledges this client as master, the {@link + * P4RuntimeController} is expected to generate a {@link + * org.onosproject.net.device.DeviceAgentEvent} with type {@link + * org.onosproject.net.device.DeviceAgentEvent.Type#ROLE_MASTER}. + */ + void runForMastership(); + + /** + * Returns true if this client is master for the server, false otherwise. + * + * @return boolean + */ + boolean isMaster(); + + /** + * Sends a packet-out for the given pipeconf. + * + * @param packet packet-out operation to be performed by the device + * @param pipeconf pipeconf currently deployed on the device + */ + void packetOut(PiPacketOperation packet, PiPipeconf pipeconf); +} diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeWriteClient.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeWriteClient.java new file mode 100644 index 0000000000..6d8dbfe500 --- /dev/null +++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeWriteClient.java @@ -0,0 +1,354 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.api; + +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiEntity; +import org.onosproject.net.pi.runtime.PiEntityType; +import org.onosproject.net.pi.runtime.PiHandle; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +/** + * P4Runtime client interface for the Write RPC that allows inserting, modifying + * and deleting PI entities. Allows batching of write requests and it returns a + * detailed response for each PI entity in the request. + */ +public interface P4RuntimeWriteClient { + + /** + * Returns a new {@link WriteRequest} instance that can be used to build a + * batched write request, for the given pipeconf. + * + * @param pipeconf pipeconf + * @return new write request + */ + WriteRequest write(PiPipeconf pipeconf); + + /** + * Signals the type of write operation for a given PI entity. + */ + enum UpdateType { + /** + * Inserts an entity. + */ + INSERT, + /** + * Modifies an existing entity. + */ + MODIFY, + /** + * Deletes an existing entity. + */ + DELETE + } + + /** + * Signals if the entity was written successfully or not. + */ + enum WriteResponseStatus { + /** + * The entity was written successfully, no errors occurred. + */ + OK, + /** + * The server didn't return any status for the entity. + */ + PENDING, + /** + * The entity was not added to the write request because it was not + * possible to encode/decode it. + */ + CODEC_ERROR, + /** + * Server responded that it was not possible to insert the entity as + * another one with the same handle already exists. + */ + ALREADY_EXIST, + /** + * Server responded that it was not possible to modify or delete the + * entity as the same cannot be found on the server. + */ + NOT_FOUND, + /** + * Other error. See {@link WriteEntityResponse#explanation()} or {@link + * WriteEntityResponse#throwable()} for more details. + */ + OTHER_ERROR, + } + + /** + * Signals the atomicity mode that the server should follow when executing a + * write request. For more information on each atomicity mode please see the + * P4Runtime spec. + */ + enum Atomicity { + /** + * Continue on error. Default value for all write requests. + */ + CONTINUE_ON_ERROR, + /** + * Rollback on error. + */ + ROLLBACK_ON_ERROR, + /** + * Dataplane atomic. + */ + DATAPLANE_ATOMIC, + } + + /** + * Abstraction of a P4Runtime write request that follows the builder + * pattern. Multiple entities can be added to the same request before + * submitting it. The implementation should guarantee that entities are + * added in the final P4Runtime protobuf message in the same order as added + * in this write request. + */ + interface WriteRequest { + + /** + * Sets the atomicity mode of this write request. Default value is + * {@link Atomicity#CONTINUE_ON_ERROR}. + * + * @param atomicity atomicity mode + * @return this + */ + WriteRequest withAtomicity(Atomicity atomicity); + + /** + * Requests to insert one PI entity. + * + * @param entity PI entity + * @return this + */ + WriteRequest insert(PiEntity entity); + + /** + * Requests to insert multiple PI entities. + * + * @param entities iterable of PI entities + * @return this + */ + WriteRequest insert(Iterable entities); + + /** + * Requests to modify one PI entity. + * + * @param entity PI entity + * @return this + */ + WriteRequest modify(PiEntity entity); + + /** + * Requests to modify multiple PI entities. + * + * @param entities iterable of PI entities + * @return this + */ + WriteRequest modify(Iterable entities); + + /** + * Requests to delete one PI entity identified by the given handle. + * + * @param handle PI handle + * @return this + */ + WriteRequest delete(PiHandle handle); + + /** + * Requests to delete multiple PI entities identified by the given + * handles. + * + * @param handles iterable of handles + * @return this + */ + WriteRequest delete(Iterable handles); + + /** + * Requests to write the given PI entity with the given update type. If + * {@code updateType} is {@link UpdateType#DELETE}, then only the handle + * will be considered by the request. + * + * @param entity PI entity + * @param updateType update type + * @return this + */ + WriteRequest entity(PiEntity entity, UpdateType updateType); + + /** + * Requests to write the given PI entities with the given update type. + * If {@code updateType} is {@link UpdateType#DELETE}, then only the + * handles will be considered by the request. + * + * @param entities iterable of PI entity + * @param updateType update type + * @return this + */ + WriteRequest entities(Iterable entities, UpdateType updateType); + + /** + * Submits this write request to the server and returns a completable + * future holding the response. The future is completed only after the + * server signals that all entities are written. + * + * @return completable future of the write response + */ + CompletableFuture submit(); + + /** + * Similar to {@link #submit()}, but blocks until the operation is + * completed, after which, it returns a read response. + * + * @return read response + */ + P4RuntimeWriteClient.WriteResponse submitSync(); + } + + /** + * Abstraction of a response obtained from a P4Runtime server after a write + * request is submitted. It allows returning a detailed response ({@link + * WriteEntityResponse}) for each PI entity in the original request. Entity + * responses are guaranteed to be returned in the same order as the + * corresponding PI entity in the request. + */ + interface WriteResponse { + + /** + * Returns true if all entities in the request were successfully + * written. In other words, if no errors occurred. False otherwise. + * + * @return true if all entities were written successfully, false + * otherwise + */ + boolean isSuccess(); + + /** + * Returns a detailed response for each PI entity in the request. The + * implementation of this method should guarantee that the returned + * collection has size equal to the number of PI entities in the + * original write request. + * + * @return collection of {@link WriteEntityResponse} + */ + Collection all(); + + /** + * Returns a detailed response for each PI entity that was successfully + * written. If {@link #isSuccess()} is {@code true}, then this method is + * expected to return the same values as {@link #all()}. + * + * @return collection of {@link WriteEntityResponse} + */ + Collection success(); + + /** + * Returns a detailed response for each PI entity for which the server + * returned an error. If {@link #isSuccess()} is {@code true}, then this + * method is expected to return an empty collection. + * + * @return collection of {@link WriteEntityResponse} + */ + Collection failed(); + + /** + * Returns a detailed response for each PI entity for which the server + * returned the given status. + * + * @param status status + * @return collection of {@link WriteEntityResponse} + */ + Collection status(WriteResponseStatus status); + } + + /** + * Represents the response of a write request for a specific PI entity. + */ + interface WriteEntityResponse { + + /** + * Returns the handle associated with the PI entity. + * + * @return handle + */ + PiHandle handle(); + + /** + * Returns the original PI entity as provided in the write request. + * Returns {@code null} if the update type was {@link + * UpdateType#DELETE}, in which case only the handle was used in the + * request. + * + * @return PI entity or null + */ + PiEntity entity(); + + /** + * Returns the type of write request performed for this entity. + * + * @return update type + */ + UpdateType updateType(); + + /** + * Returns the type of this entity. + * + * @return PI entity type + */ + PiEntityType entityType(); + + /** + * Returns true if this PI entity was written successfully, false + * otherwise. + * + * @return true if this PI entity was written successfully, false + * otherwise + */ + boolean isSuccess(); + + /** + * Returns the status for this PI entity. If {@link #isSuccess()} + * returns {@code true}, then this method is expected to return {@link + * WriteResponseStatus#OK}. If {@link WriteResponseStatus#OTHER_ERROR} + * is returned, further details might be provided in {@link + * #explanation()} and {@link #throwable()}. + * + * @return status + */ + WriteResponseStatus status(); + + /** + * If the PI entity was NOT written successfully, this method returns a + * message explaining the error occurred. Returns an empty string if + * such message is not available, or {@code null} if no errors + * occurred. + * + * @return error explanation or empty string or null + */ + String explanation(); + + /** + * If the PI entity was NOT written successfully, this method returns + * the internal throwable instance associated with the error (e.g. a + * {@link io.grpc.StatusRuntimeException} instance). Returns null if + * such throwable instance is not available or if no errors occurred. + * + * @return throwable instance associated with this PI entity + */ + Throwable throwable(); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/AbstractP4RuntimeCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/AbstractP4RuntimeCodec.java deleted file mode 100644 index a76ab7c4d8..0000000000 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/AbstractP4RuntimeCodec.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2019-present Open Networking Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.onosproject.p4runtime.ctl; - -import com.google.protobuf.Message; -import com.google.protobuf.TextFormat; -import org.onosproject.net.pi.model.PiPipeconf; -import org.onosproject.net.pi.runtime.PiEntity; -import org.slf4j.Logger; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static com.google.common.base.Preconditions.checkNotNull; -import static java.lang.String.format; -import static org.slf4j.LoggerFactory.getLogger; - -/** - * Abstract implementation of a codec that translates PI entities into P4Runtime - * protobuf messages and vice versa. - * - * @param

PI entity class - * @param P4Runtime protobuf message class - */ -abstract class AbstractP4RuntimeCodec

{ - - protected final Logger log = getLogger(this.getClass()); - - protected abstract M encode(P piEntity, PiPipeconf pipeconf, - P4InfoBrowser browser) - throws CodecException, P4InfoBrowser.NotFoundException; - - protected abstract P decode(M message, PiPipeconf pipeconf, - P4InfoBrowser browser) - throws CodecException, P4InfoBrowser.NotFoundException; - - /** - * Returns a P4Runtime protobuf message that is equivalent to the given PI - * entity for the given pipeconf. - * - * @param piEntity PI entity instance - * @param pipeconf pipeconf - * @return P4Runtime protobuf message - * @throws CodecException if the given PI entity cannot be encoded (see - * exception message) - */ - public M encode(P piEntity, PiPipeconf pipeconf) - throws CodecException { - try { - return encode(piEntity, pipeconf, browserOrFail(pipeconf)); - } catch (P4InfoBrowser.NotFoundException e) { - throw new CodecException(e.getMessage()); - } - } - - /** - * Returns a PI entity instance that is equivalent to the P4Runtime protobuf - * message for the given pipeconf. - * - * @param message P4Runtime protobuf message - * @param pipeconf pipeconf pipeconf - * @return PI entity instance - * @throws CodecException if the given protobuf message cannot be decoded - * (see exception message) - */ - public P decode(M message, PiPipeconf pipeconf) - throws CodecException { - try { - return decode(message, pipeconf, browserOrFail(pipeconf)); - } catch (P4InfoBrowser.NotFoundException e) { - throw new CodecException(e.getMessage()); - } - } - - /** - * Same as {@link #encode(PiEntity, PiPipeconf)} but returns null in case of - * exceptions, while the error message is logged. - * - * @param piEntity PI entity instance - * @param pipeconf pipeconf - * @return P4Runtime protobuf message - */ - public M encodeOrNull(P piEntity, PiPipeconf pipeconf) { - try { - return encode(piEntity, pipeconf); - } catch (CodecException e) { - log.error("Unable to encode {}: {} [{}]", - piEntity.getClass().getSimpleName(), - e.getMessage(), piEntity.toString()); - return null; - } - } - - /** - * Same as {@link #decode(Message, PiPipeconf)} but returns null in case of - * exceptions, while the error message is logged. - * - * @param message P4Runtime protobuf message - * @param pipeconf pipeconf pipeconf - * @return PI entity instance - */ - public P decodeOrNull(M message, PiPipeconf pipeconf) { - try { - return decode(message, pipeconf); - } catch (CodecException e) { - log.error("Unable to decode {}: {} [{}]", - message.getClass().getSimpleName(), - e.getMessage(), TextFormat.shortDebugString(message)); - return null; - } - } - - /** - * Encodes the given list of PI entities, skipping those that cannot be - * encoded, in which case an error message is logged. For this reason, the - * returned list might have different size than the returned one. - * - * @param piEntities list of PI entities - * @param pipeconf pipeconf - * @return list of P4Runtime protobuf messages - */ - public List encodeAll(List

piEntities, PiPipeconf pipeconf) { - checkNotNull(piEntities); - return piEntities.stream() - .map(p -> encodeOrNull(p, pipeconf)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - /** - * Decodes the given list of P4Runtime protobuf messages, skipping those - * that cannot be decoded, on which case an error message is logged. For - * this reason, the returned list might have different size than the - * returned one. - * - * @param messages list of protobuf messages - * @param pipeconf pipeconf - * @return list of PI entities - */ - public List

decodeAll(List messages, PiPipeconf pipeconf) { - checkNotNull(messages); - return messages.stream() - .map(m -> decodeOrNull(m, pipeconf)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - /** - * Same as {@link #encodeAll(List, PiPipeconf)} but throws an exception if - * one or more of the given PI entities cannot be encoded. The returned list - * is guaranteed to have same size and order as the given one. - * - * @param piEntities list of PI entities - * @param pipeconf pipeconf - * @return list of protobuf messages - * @throws CodecException if one or more of the given PI entities cannot be - * encoded - */ - public List encodeAllOrFail(List

piEntities, PiPipeconf pipeconf) - throws CodecException { - final List messages = encodeAll(piEntities, pipeconf); - if (piEntities.size() != messages.size()) { - throw new CodecException(format( - "Unable to encode %d entities of %d given " + - "(see previous logs for details)", - piEntities.size() - messages.size(), piEntities.size())); - } - return messages; - } - - /** - * Same as {@link #decodeAll(List, PiPipeconf)} but throws an exception if - * one or more of the given protobuf messages cannot be decoded. The - * returned list is guaranteed to have same size and order as the given - * one. - * - * @param messages list of protobuf messages - * @param pipeconf pipeconf - * @return list of PI entities - * @throws CodecException if one or more of the given protobuf messages - * cannot be decoded - */ - public List

decodeAllOrFail(List messages, PiPipeconf pipeconf) - throws CodecException { - final List

piEntities = decodeAll(messages, pipeconf); - if (messages.size() != piEntities.size()) { - throw new CodecException(format( - "Unable to decode %d messages of %d given " + - "(see previous logs for details)", - messages.size() - piEntities.size(), messages.size())); - } - return piEntities; - } - - private P4InfoBrowser browserOrFail(PiPipeconf pipeconf) throws CodecException { - final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - if (browser == null) { - throw new CodecException(format( - "Unable to get P4InfoBrowser for pipeconf %s", pipeconf.id())); - } - return browser; - } -} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileMemberCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileMemberCodec.java deleted file mode 100644 index 9567b0a1c5..0000000000 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileMemberCodec.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2019-present Open Networking Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.onosproject.p4runtime.ctl; - -import org.onosproject.net.pi.model.PiActionProfileId; -import org.onosproject.net.pi.model.PiPipeconf; -import org.onosproject.net.pi.runtime.PiActionProfileMember; -import org.onosproject.net.pi.runtime.PiActionProfileMemberId; -import p4.config.v1.P4InfoOuterClass; -import p4.v1.P4RuntimeOuterClass; -import p4.v1.P4RuntimeOuterClass.ActionProfileMember; - -import static org.onosproject.p4runtime.ctl.TableEntryEncoder.decodeActionMsg; -import static org.onosproject.p4runtime.ctl.TableEntryEncoder.encodePiAction; -/** - * Codec for P4Runtime ActionProfileMember. - */ -final class ActionProfileMemberCodec - extends AbstractP4RuntimeCodec { - - @Override - public ActionProfileMember encode(PiActionProfileMember piEntity, - PiPipeconf pipeconf, - P4InfoBrowser browser) - throws CodecException, P4InfoBrowser.NotFoundException { - final ActionProfileMember.Builder actionProfileMemberBuilder = - ActionProfileMember.newBuilder(); - // Member ID - actionProfileMemberBuilder.setMemberId(piEntity.id().id()); - // Action profile ID - P4InfoOuterClass.ActionProfile actionProfile = - browser.actionProfiles().getByName(piEntity.actionProfile().id()); - final int actionProfileId = actionProfile.getPreamble().getId(); - actionProfileMemberBuilder.setActionProfileId(actionProfileId); - // Action - final P4RuntimeOuterClass.Action action = encodePiAction(piEntity.action(), browser); - actionProfileMemberBuilder.setAction(action); - return actionProfileMemberBuilder.build(); - } - - @Override - public PiActionProfileMember decode(ActionProfileMember message, - PiPipeconf pipeconf, - P4InfoBrowser browser) - throws CodecException, P4InfoBrowser.NotFoundException { - final PiActionProfileId actionProfileId = PiActionProfileId.of( - browser.actionProfiles() - .getById(message.getActionProfileId()) - .getPreamble() - .getName()); - return PiActionProfileMember.builder() - .forActionProfile(actionProfileId) - .withId(PiActionProfileMemberId.of(message.getMemberId())) - .withAction(decodeActionMsg(message.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 deleted file mode 100644 index 3765d247f0..0000000000 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright 2017-present Open Networking Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.onosproject.p4runtime.ctl; - -import org.onosproject.net.pi.model.PiCounterId; -import org.onosproject.net.pi.model.PiCounterType; -import org.onosproject.net.pi.model.PiPipeconf; -import org.onosproject.net.pi.model.PiTableId; -import org.onosproject.net.pi.runtime.PiCounterCell; -import org.onosproject.net.pi.runtime.PiCounterCellId; -import org.onosproject.net.pi.runtime.PiTableEntry; -import org.slf4j.Logger; -import p4.v1.P4RuntimeOuterClass; -import p4.v1.P4RuntimeOuterClass.CounterData; -import p4.v1.P4RuntimeOuterClass.CounterEntry; -import p4.v1.P4RuntimeOuterClass.DirectCounterEntry; -import p4.v1.P4RuntimeOuterClass.Entity; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static java.lang.String.format; -import static org.onosproject.p4runtime.ctl.P4RuntimeUtils.indexMsg; -import static org.slf4j.LoggerFactory.getLogger; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.COUNTER_ENTRY; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.DIRECT_COUNTER_ENTRY; - -/** - * Encoder/decoder of PI counter IDs to counter entry protobuf messages, and - * vice versa. - */ -final class CounterEntryCodec { - - private static final Logger log = getLogger(CounterEntryCodec.class); - - private CounterEntryCodec() { - // Hides constructor. - } - - /** - * Returns a collection of P4Runtime entity protobuf messages describing - * both counter or direct counter entries, encoded from the given collection - * of PI counter cell identifiers, for the given pipeconf. If a PI counter - * cell identifier cannot be encoded, it is skipped, hence the returned - * collection might have different size than the input one. - * - * @param cellIds counter cell identifiers - * @param pipeconf pipeconf - * @return collection of entity messages describing both counter or direct - * counter entries - */ - static List encodePiCounterCellIds(List cellIds, - PiPipeconf pipeconf) { - - final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - - if (browser == null) { - log.error("Unable to get a P4Info browser for pipeconf {}", pipeconf.id()); - return Collections.emptyList(); - } - - return cellIds - .stream() - .map(cellId -> { - try { - return encodePiCounterCellId(cellId, pipeconf, browser); - } catch (P4InfoBrowser.NotFoundException | CodecException e) { - log.warn("Unable to encode PI counter cell id: {}", e.getMessage()); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - /** - * Returns a collection of P4Runtime entity protobuf messages to be used in - * requests to read all cells from the given counter identifiers. Works for - * both indirect or direct counters. If a PI counter identifier cannot be - * encoded, it is skipped, hence the returned collection might have - * different size than the input one. - * - * @param counterIds counter identifiers - * @param pipeconf pipeconf - * @return collection of entity messages - */ - static List readAllCellsEntities(List counterIds, - PiPipeconf pipeconf) { - final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - - if (browser == null) { - log.error("Unable to get a P4Info browser for pipeconf {}", pipeconf.id()); - return Collections.emptyList(); - } - - return counterIds - .stream() - .map(counterId -> { - try { - return readAllCellsEntity(counterId, pipeconf, browser); - } catch (P4InfoBrowser.NotFoundException | CodecException e) { - log.warn("Unable to encode counter ID to read-all-cells entity: {}", - e.getMessage()); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - /** - * Returns a collection of PI counter cell data, decoded from the given - * P4Runtime entity protobuf messages describing both counter or direct - * counter entries, and pipeconf. If an entity message cannot be encoded, it - * is skipped, hence the returned collection might have different size than - * the input one. - * - * @param entities P4Runtime entity messages - * @param pipeconf pipeconf - * @return collection of PI counter cell data - */ - static List decodeCounterEntities(List entities, - PiPipeconf pipeconf) { - - final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - - if (browser == null) { - log.error("Unable to get a P4Info browser for pipeconf {}", pipeconf.id()); - return Collections.emptyList(); - } - - return entities - .stream() - .filter(entity -> entity.getEntityCase() == COUNTER_ENTRY || - entity.getEntityCase() == DIRECT_COUNTER_ENTRY) - .map(entity -> { - try { - return decodeCounterEntity(entity, pipeconf, browser); - } catch (CodecException | P4InfoBrowser.NotFoundException e) { - log.warn("Unable to decode counter entity message: {}", - e.getMessage()); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - private static Entity encodePiCounterCellId(PiCounterCellId cellId, - PiPipeconf pipeconf, - P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - - int counterId; - Entity entity; - // Encode PI cell ID into entity message and add to read request. - switch (cellId.counterType()) { - case INDIRECT: - counterId = browser.counters() - .getByName(cellId.counterId().id()) - .getPreamble() - .getId(); - entity = Entity.newBuilder() - .setCounterEntry( - CounterEntry.newBuilder() - .setCounterId(counterId) - .setIndex(indexMsg(cellId.index())) - .build()) - .build(); - break; - case DIRECT: - DirectCounterEntry.Builder entryBuilder = DirectCounterEntry.newBuilder(); - entryBuilder.setTableEntry( - TableEntryEncoder.encode(cellId.tableEntry(), pipeconf)); - entity = Entity.newBuilder() - .setDirectCounterEntry(entryBuilder.build()) - .build(); - break; - default: - throw new CodecException(format( - "Unrecognized PI counter cell ID type '%s'", - cellId.counterType())); - } - - return entity; - } - - private static Entity readAllCellsEntity(PiCounterId counterId, - PiPipeconf pipeconf, - P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - - if (!pipeconf.pipelineModel().counter(counterId).isPresent()) { - throw new CodecException(format( - "not such counter '%s' in pipeline model", counterId)); - } - final PiCounterType counterType = pipeconf.pipelineModel() - .counter(counterId).get().counterType(); - - switch (counterType) { - case INDIRECT: - final int p4InfoCounterId = browser.counters() - .getByName(counterId.id()) - .getPreamble().getId(); - return Entity.newBuilder().setCounterEntry( - P4RuntimeOuterClass.CounterEntry.newBuilder() - // Index unset to read all cells - .setCounterId(p4InfoCounterId) - .build()) - .build(); - case DIRECT: - final PiTableId tableId = pipeconf.pipelineModel() - .counter(counterId).get().table(); - if (tableId == null) { - throw new CodecException(format( - "null table for direct counter '%s'", counterId)); - } - final int p4TableId = browser.tables().getByName(tableId.id()) - .getPreamble().getId(); - return Entity.newBuilder().setDirectCounterEntry( - P4RuntimeOuterClass.DirectCounterEntry.newBuilder() - .setTableEntry( - // Match unset to read all cells - P4RuntimeOuterClass.TableEntry.newBuilder() - .setTableId(p4TableId) - .build()) - .build()) - .build(); - default: - throw new CodecException(format( - "unrecognized PI counter type '%s'", counterType)); - } - } - - private static PiCounterCell decodeCounterEntity(Entity entity, - PiPipeconf pipeconf, - P4InfoBrowser browser) - throws CodecException, P4InfoBrowser.NotFoundException { - - CounterData counterData; - PiCounterCellId piCellId; - - if (entity.getEntityCase() == COUNTER_ENTRY) { - String counterName = browser.counters() - .getById(entity.getCounterEntry().getCounterId()) - .getPreamble() - .getName(); - piCellId = PiCounterCellId.ofIndirect( - PiCounterId.of(counterName), - entity.getCounterEntry().getIndex().getIndex()); - counterData = entity.getCounterEntry().getData(); - } else if (entity.getEntityCase() == DIRECT_COUNTER_ENTRY) { - PiTableEntry piTableEntry = TableEntryEncoder.decode( - entity.getDirectCounterEntry().getTableEntry(), pipeconf); - piCellId = PiCounterCellId.ofDirect(piTableEntry); - counterData = entity.getDirectCounterEntry().getData(); - } else { - throw new CodecException(format( - "Unrecognized entity type '%s' in P4Runtime message", - entity.getEntityCase().name())); - } - - return new PiCounterCell(piCellId, - counterData.getPacketCount(), - counterData.getByteCount()); - } -} 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 deleted file mode 100644 index 44f27d1fde..0000000000 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MeterEntryCodec.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright 2017-present Open Networking Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.onosproject.p4runtime.ctl; - -import org.onosproject.net.pi.model.PiMeterId; -import org.onosproject.net.pi.model.PiMeterType; -import org.onosproject.net.pi.model.PiPipeconf; -import org.onosproject.net.pi.model.PiTableId; -import org.onosproject.net.pi.runtime.PiMeterBand; -import org.onosproject.net.pi.runtime.PiMeterCellConfig; -import org.onosproject.net.pi.runtime.PiMeterCellId; -import org.onosproject.net.pi.runtime.PiTableEntry; -import org.slf4j.Logger; -import p4.v1.P4RuntimeOuterClass; -import p4.v1.P4RuntimeOuterClass.DirectMeterEntry; -import p4.v1.P4RuntimeOuterClass.Entity; -import p4.v1.P4RuntimeOuterClass.MeterConfig; -import p4.v1.P4RuntimeOuterClass.MeterEntry; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static java.lang.String.format; -import static org.onosproject.p4runtime.ctl.P4RuntimeUtils.indexMsg; -import static org.slf4j.LoggerFactory.getLogger; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.DIRECT_METER_ENTRY; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.METER_ENTRY; - -/** - * Encoder/decoder of PI meter cell configurations to meter entry protobuf - * messages, and vice versa. - */ -final class MeterEntryCodec { - - private static final Logger log = getLogger(MeterEntryCodec.class); - - private MeterEntryCodec() { - // Hides constructor. - } - - /** - * Returns a collection of P4Runtime entity protobuf messages describing - * both meter or direct meter entries, encoded from the given collection of - * PI meter cell configurations, for the given pipeconf. If a PI meter cell - * configurations cannot be encoded, it is skipped, hence the returned - * collection might have different size than the input one. - * - * @param cellConfigs meter cell configurations - * @param pipeconf pipeconf - * @return collection of entity messages describing both meter or direct - * meter entries - */ - static List encodePiMeterCellConfigs(List cellConfigs, - PiPipeconf pipeconf) { - final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - - if (browser == null) { - log.error("Unable to get a P4Info browser for pipeconf {}", pipeconf.id()); - return Collections.emptyList(); - } - - return cellConfigs - .stream() - .map(cellConfig -> { - try { - return encodePiMeterCellConfig(cellConfig, pipeconf, browser); - } catch (P4InfoBrowser.NotFoundException | CodecException e) { - log.warn("Unable to encode PI meter cell id: {}", e.getMessage()); - log.debug("exception", e); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - /** - * Returns a collection of P4Runtime entity protobuf messages to be used in - * requests to read all cells from the given meter identifiers. Works for - * both indirect or direct meters. If a PI meter identifier cannot be - * encoded, it is skipped, hence the returned collection might have - * different size than the input one. - * - * @param meterIds meter identifiers - * @param pipeconf pipeconf - * @return collection of entity messages - */ - static List readAllCellsEntities(List meterIds, - PiPipeconf pipeconf) { - final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - - if (browser == null) { - log.error("Unable to get a P4Info browser for pipeconf {}", pipeconf.id()); - return Collections.emptyList(); - } - - return meterIds - .stream() - .map(meterId -> { - try { - return readAllCellsEntity(meterId, pipeconf, browser); - } catch (P4InfoBrowser.NotFoundException | CodecException e) { - log.warn("Unable to encode meter ID to read-all-cells entity: {}", - e.getMessage()); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - /** - * Returns a collection of PI meter cell configurations, decoded from the - * given P4Runtime entity protobuf messages describing both meter or direct - * meter entries, and pipeconf. If an entity message cannot be encoded, it - * is skipped, hence the returned collection might have different size than - * the input one. - * - * @param entities P4Runtime entity messages - * @param pipeconf pipeconf - * @return collection of PI meter cell data - */ - static List decodeMeterEntities(List entities, - PiPipeconf pipeconf) { - final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - - if (browser == null) { - log.error("Unable to get a P4Info browser for pipeconf {}", pipeconf.id()); - return Collections.emptyList(); - } - - return entities - .stream() - .filter(entity -> entity.getEntityCase() == METER_ENTRY || - entity.getEntityCase() == DIRECT_METER_ENTRY) - .map(entity -> { - try { - return decodeMeterEntity(entity, pipeconf, browser); - } catch (CodecException | P4InfoBrowser.NotFoundException e) { - log.warn("Unable to decode meter entity message: {}", e.getMessage()); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - private static Entity encodePiMeterCellConfig(PiMeterCellConfig config, - PiPipeconf pipeconf, - P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - - int meterId; - Entity entity; - MeterConfig meterConfig; - - PiMeterBand[] bands = config.meterBands() - .toArray(new PiMeterBand[config.meterBands().size()]); - if (bands.length == 2) { - long cir, cburst, pir, pburst; - // The band with bigger burst is peak if rate of them is equal. - if (bands[0].rate() > bands[1].rate() || - (bands[0].rate() == bands[1].rate() && - bands[0].burst() >= bands[1].burst())) { - cir = bands[1].rate(); - cburst = bands[1].burst(); - pir = bands[0].rate(); - pburst = bands[0].burst(); - } else { - cir = bands[0].rate(); - cburst = bands[0].burst(); - pir = bands[1].rate(); - pburst = bands[1].burst(); - } - meterConfig = MeterConfig.newBuilder() - .setCir(cir) - .setCburst(cburst) - .setPir(pir) - .setPburst(pburst) - .build(); - } else if (bands.length == 0) { - // When reading meter cells. - meterConfig = null; - } else { - throw new CodecException("number of meter bands should be either 2 or 0"); - } - - switch (config.cellId().meterType()) { - case INDIRECT: - meterId = browser.meters() - .getByName(config.cellId().meterId().id()) - .getPreamble().getId(); - MeterEntry.Builder indEntryBuilder = MeterEntry.newBuilder() - .setMeterId(meterId) - .setIndex(indexMsg(config.cellId().index())); - if (meterConfig != null) { - indEntryBuilder.setConfig(meterConfig); - } - entity = Entity.newBuilder() - .setMeterEntry(indEntryBuilder.build()).build(); - break; - case DIRECT: - DirectMeterEntry.Builder dirEntryBuilder = DirectMeterEntry.newBuilder() - .setTableEntry(TableEntryEncoder.encode( - config.cellId().tableEntry(), pipeconf)); - if (meterConfig != null) { - dirEntryBuilder.setConfig(meterConfig); - } - entity = Entity.newBuilder() - .setDirectMeterEntry(dirEntryBuilder.build()).build(); - break; - default: - throw new CodecException(format("unrecognized PI meter type '%s'", - config.cellId().meterType())); - } - - return entity; - } - - private static Entity readAllCellsEntity(PiMeterId meterId, - PiPipeconf pipeconf, - P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - - if (!pipeconf.pipelineModel().meter(meterId).isPresent()) { - throw new CodecException(format( - "not such meter '%s' in pipeline model", meterId)); - } - final PiMeterType meterType = pipeconf.pipelineModel() - .meter(meterId).get().meterType(); - - switch (meterType) { - case INDIRECT: - final int p4InfoMeterId = browser.meters() - .getByName(meterId.id()) - .getPreamble().getId(); - return Entity.newBuilder().setMeterEntry( - P4RuntimeOuterClass.MeterEntry.newBuilder() - // Index unset to read all cells - .setMeterId(p4InfoMeterId) - .build()) - .build(); - case DIRECT: - final PiTableId tableId = pipeconf.pipelineModel() - .meter(meterId).get().table(); - if (tableId == null) { - throw new CodecException(format( - "null table for direct meter '%s'", meterId)); - } - final int p4TableId = browser.tables().getByName(tableId.id()) - .getPreamble().getId(); - return Entity.newBuilder().setDirectMeterEntry( - P4RuntimeOuterClass.DirectMeterEntry.newBuilder() - .setTableEntry( - // Match unset to read all cells - P4RuntimeOuterClass.TableEntry.newBuilder() - .setTableId(p4TableId) - .build()) - .build()) - .build(); - default: - throw new CodecException(format( - "unrecognized PI meter type '%s'", meterType)); - } - } - - private static PiMeterCellConfig decodeMeterEntity(Entity entity, - PiPipeconf pipeconf, - P4InfoBrowser browser) - throws CodecException, P4InfoBrowser.NotFoundException { - - MeterConfig meterConfig; - PiMeterCellId piCellId; - - if (entity.getEntityCase() == METER_ENTRY) { - String meterName = browser.meters() - .getById(entity.getMeterEntry().getMeterId()) - .getPreamble() - .getName(); - piCellId = PiMeterCellId.ofIndirect( - PiMeterId.of(meterName), - entity.getMeterEntry().getIndex().getIndex()); - meterConfig = entity.getMeterEntry().getConfig(); - } else if (entity.getEntityCase() == DIRECT_METER_ENTRY) { - PiTableEntry piTableEntry = TableEntryEncoder.decode( - entity.getDirectMeterEntry().getTableEntry(), - pipeconf); - piCellId = PiMeterCellId.ofDirect(piTableEntry); - meterConfig = entity.getDirectMeterEntry().getConfig(); - } else { - throw new CodecException(format( - "unrecognized entity type '%s' in P4Runtime message", - entity.getEntityCase().name())); - } - - return PiMeterCellConfig.builder() - .withMeterCellId(piCellId) - .withMeterBand(new PiMeterBand(meterConfig.getCir(), - meterConfig.getCburst())) - .withMeterBand(new PiMeterBand(meterConfig.getPir(), - meterConfig.getPburst())) - .build(); - } -} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MulticastGroupEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MulticastGroupEntryCodec.java deleted file mode 100644 index 5f55c1fc50..0000000000 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/MulticastGroupEntryCodec.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2018-present Open Networking Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.onosproject.p4runtime.ctl; - -import org.onosproject.net.PortNumber; -import org.onosproject.net.pi.runtime.PiMulticastGroupEntry; -import org.onosproject.net.pi.runtime.PiPreReplica; -import p4.v1.P4RuntimeOuterClass.MulticastGroupEntry; -import p4.v1.P4RuntimeOuterClass.Replica; - -import static java.lang.String.format; - -/** - * A coded of {@link PiMulticastGroupEntry} to P4Runtime MulticastGroupEntry - * messages, and vice versa. - */ -final class MulticastGroupEntryCodec { - - private MulticastGroupEntryCodec() { - // Hides constructor. - } - - /** - * Returns a P4Runtime MulticastGroupEntry message equivalent to the given - * PiMulticastGroupEntry. - * - * @param piEntry PiMulticastGroupEntry - * @return P4Runtime MulticastGroupEntry message - * @throws CodecException if the PiMulticastGroupEntry cannot be encoded. - */ - static MulticastGroupEntry encode(PiMulticastGroupEntry piEntry) throws CodecException { - final MulticastGroupEntry.Builder msgBuilder = MulticastGroupEntry.newBuilder(); - msgBuilder.setMulticastGroupId(piEntry.groupId()); - for (PiPreReplica replica : piEntry.replicas()) { - final int p4PortId; - try { - p4PortId = Math.toIntExact(replica.egressPort().toLong()); - } catch (ArithmeticException e) { - throw new CodecException(format( - "Cannot cast 64bit port value '%s' to 32bit", - replica.egressPort())); - } - msgBuilder.addReplicas( - Replica.newBuilder() - .setEgressPort(p4PortId) - .setInstance(replica.instanceId()) - .build()); - } - return msgBuilder.build(); - } - - /** - * Returns a PiMulticastGroupEntry equivalent to the given P4Runtime - * MulticastGroupEntry message. - * - * @param msg P4Runtime MulticastGroupEntry message - * @return PiMulticastGroupEntry - */ - static PiMulticastGroupEntry decode(MulticastGroupEntry msg) { - final PiMulticastGroupEntry.Builder piEntryBuilder = PiMulticastGroupEntry.builder(); - piEntryBuilder.withGroupId(msg.getMulticastGroupId()); - msg.getReplicasList().stream() - .map(r -> new PiPreReplica( - PortNumber.portNumber(r.getEgressPort()), r.getInstance())) - .forEach(piEntryBuilder::addReplica); - return piEntryBuilder.build(); - } -} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java deleted file mode 100644 index 6ac478f050..0000000000 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java +++ /dev/null @@ -1,1282 +0,0 @@ -/* - * Copyright 2017-present Open Networking Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.onosproject.p4runtime.ctl; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; -import io.grpc.ManagedChannel; -import io.grpc.Metadata; -import io.grpc.Status; -import io.grpc.StatusRuntimeException; -import io.grpc.protobuf.lite.ProtoLiteUtils; -import io.grpc.stub.ClientCallStreamObserver; -import io.grpc.stub.StreamObserver; -import org.onlab.osgi.DefaultServiceDirectory; -import org.onlab.util.Tools; -import org.onosproject.grpc.ctl.AbstractGrpcClient; -import org.onosproject.net.pi.model.PiActionProfileId; -import org.onosproject.net.pi.model.PiCounterId; -import org.onosproject.net.pi.model.PiMeterId; -import org.onosproject.net.pi.model.PiPipeconf; -import org.onosproject.net.pi.model.PiTableId; -import org.onosproject.net.pi.runtime.PiActionProfileGroup; -import org.onosproject.net.pi.runtime.PiActionProfileMember; -import org.onosproject.net.pi.runtime.PiActionProfileMemberId; -import org.onosproject.net.pi.runtime.PiCounterCell; -import org.onosproject.net.pi.runtime.PiCounterCellId; -import org.onosproject.net.pi.runtime.PiMeterCellConfig; -import org.onosproject.net.pi.runtime.PiMeterCellId; -import org.onosproject.net.pi.runtime.PiMulticastGroupEntry; -import org.onosproject.net.pi.runtime.PiPacketOperation; -import org.onosproject.net.pi.runtime.PiTableEntry; -import org.onosproject.net.pi.service.PiPipeconfService; -import org.onosproject.p4runtime.api.P4RuntimeClient; -import org.onosproject.p4runtime.api.P4RuntimeClientKey; -import org.onosproject.p4runtime.api.P4RuntimeEvent; -import p4.config.v1.P4InfoOuterClass.P4Info; -import p4.tmp.P4Config; -import p4.v1.P4RuntimeGrpc; -import p4.v1.P4RuntimeOuterClass; -import p4.v1.P4RuntimeOuterClass.ActionProfileGroup; -import p4.v1.P4RuntimeOuterClass.ActionProfileMember; -import p4.v1.P4RuntimeOuterClass.Entity; -import p4.v1.P4RuntimeOuterClass.ForwardingPipelineConfig; -import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest; -import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigResponse; -import p4.v1.P4RuntimeOuterClass.MasterArbitrationUpdate; -import p4.v1.P4RuntimeOuterClass.MulticastGroupEntry; -import p4.v1.P4RuntimeOuterClass.PacketReplicationEngineEntry; -import p4.v1.P4RuntimeOuterClass.ReadRequest; -import p4.v1.P4RuntimeOuterClass.ReadResponse; -import p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest; -import p4.v1.P4RuntimeOuterClass.StreamMessageRequest; -import p4.v1.P4RuntimeOuterClass.StreamMessageResponse; -import p4.v1.P4RuntimeOuterClass.TableEntry; -import p4.v1.P4RuntimeOuterClass.Uint128; -import p4.v1.P4RuntimeOuterClass.Update; -import p4.v1.P4RuntimeOuterClass.WriteRequest; - -import java.math.BigInteger; -import java.net.ConnectException; -import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Stream; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; -import static org.onosproject.p4runtime.ctl.P4RuntimeCodecs.CODECS; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.ACTION_PROFILE_GROUP; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.ACTION_PROFILE_MEMBER; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.COUNTER_ENTRY; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.DIRECT_COUNTER_ENTRY; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.DIRECT_METER_ENTRY; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.METER_ENTRY; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.PACKET_REPLICATION_ENGINE_ENTRY; -import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.TABLE_ENTRY; -import static p4.v1.P4RuntimeOuterClass.PacketIn; -import static p4.v1.P4RuntimeOuterClass.PacketOut; -import static p4.v1.P4RuntimeOuterClass.PacketReplicationEngineEntry.TypeCase.MULTICAST_GROUP_ENTRY; -import static p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest.Action.VERIFY_AND_COMMIT; - -/** - * Implementation of a P4Runtime client. - */ -final class P4RuntimeClientImpl extends AbstractGrpcClient implements P4RuntimeClient { - - private static final String MISSING_P4INFO_BROWSER = "Unable to get a P4Info browser for pipeconf {}"; - - private static final Metadata.Key STATUS_DETAILS_KEY = - Metadata.Key.of( - "grpc-status-details-bin", - ProtoLiteUtils.metadataMarshaller( - com.google.rpc.Status.getDefaultInstance())); - - private static final Map UPDATE_TYPES = ImmutableMap.of( - WriteOperationType.UNSPECIFIED, Update.Type.UNSPECIFIED, - WriteOperationType.INSERT, Update.Type.INSERT, - WriteOperationType.MODIFY, Update.Type.MODIFY, - WriteOperationType.DELETE, Update.Type.DELETE - ); - - private final long p4DeviceId; - private final P4RuntimeControllerImpl controller; - private final P4RuntimeGrpc.P4RuntimeBlockingStub blockingStub; - private StreamChannelManager streamChannelManager; - - // Used by this client for write requests. - private Uint128 clientElectionId = Uint128.newBuilder().setLow(1).build(); - - private final AtomicBoolean isClientMaster = new AtomicBoolean(false); - - /** - * Default constructor. - * - * @param clientKey the client key of this client - * @param channel gRPC channel - * @param controller runtime client controller - */ - P4RuntimeClientImpl(P4RuntimeClientKey clientKey, ManagedChannel channel, - P4RuntimeControllerImpl controller) { - - super(clientKey); - this.p4DeviceId = clientKey.p4DeviceId(); - this.controller = controller; - - //TODO Investigate use of stub deadlines instead of timeout in supplyInContext - this.blockingStub = P4RuntimeGrpc.newBlockingStub(channel); - this.streamChannelManager = new StreamChannelManager(channel); - } - - @Override - public CompletableFuture startStreamChannel() { - return supplyInContext(() -> sendMasterArbitrationUpdate(false), - "start-initStreamChannel"); - } - - @Override - public CompletableFuture becomeMaster() { - return supplyInContext(() -> sendMasterArbitrationUpdate(true), - "becomeMaster"); - } - - @Override - public boolean isMaster() { - return streamChannelManager.isOpen() && isClientMaster.get(); - } - - @Override - public boolean isStreamChannelOpen() { - return streamChannelManager.isOpen(); - } - - @Override - public CompletableFuture setPipelineConfig(PiPipeconf pipeconf, ByteBuffer deviceData) { - return supplyInContext(() -> doSetPipelineConfig(pipeconf, deviceData), "setPipelineConfig"); - } - - @Override - public boolean isPipelineConfigSet(PiPipeconf pipeconf, ByteBuffer deviceData) { - return doIsPipelineConfigSet(pipeconf, deviceData); - } - - @Override - public CompletableFuture writeTableEntries(List piTableEntries, - WriteOperationType opType, PiPipeconf pipeconf) { - return supplyInContext(() -> doWriteTableEntries(piTableEntries, opType, pipeconf), - "writeTableEntries-" + opType.name()); - } - - @Override - public CompletableFuture> dumpTables( - Set piTableIds, boolean defaultEntries, PiPipeconf pipeconf) { - return supplyInContext(() -> doDumpTables(piTableIds, defaultEntries, pipeconf), - "dumpTables-" + piTableIds.hashCode()); - } - - @Override - public CompletableFuture> dumpAllTables(PiPipeconf pipeconf) { - return supplyInContext(() -> doDumpTables(null, false, pipeconf), "dumpAllTables"); - } - - @Override - public CompletableFuture packetOut(PiPacketOperation packet, PiPipeconf pipeconf) { - return supplyInContext(() -> doPacketOut(packet, pipeconf), "packetOut"); - } - - @Override - public CompletableFuture> readCounterCells(Set cellIds, - PiPipeconf pipeconf) { - return supplyInContext(() -> doReadCounterCells(Lists.newArrayList(cellIds), pipeconf), - "readCounterCells-" + cellIds.hashCode()); - } - - @Override - public CompletableFuture> readAllCounterCells(Set counterIds, - PiPipeconf pipeconf) { - return supplyInContext(() -> doReadAllCounterCells(Lists.newArrayList(counterIds), pipeconf), - "readAllCounterCells-" + counterIds.hashCode()); - } - - @Override - public CompletableFuture writeActionProfileMembers(List members, - WriteOperationType opType, - PiPipeconf pipeconf) { - return supplyInContext(() -> doWriteActionProfileMembers(members, opType, pipeconf), - "writeActionProfileMembers-" + opType.name()); - } - - - @Override - public CompletableFuture writeActionProfileGroup(PiActionProfileGroup group, - WriteOperationType opType, - PiPipeconf pipeconf) { - return supplyInContext(() -> doWriteActionProfileGroup(group, opType, pipeconf), - "writeActionProfileGroup-" + opType.name()); - } - - @Override - public CompletableFuture> dumpActionProfileGroups( - PiActionProfileId actionProfileId, PiPipeconf pipeconf) { - return supplyInContext(() -> doDumpGroups(actionProfileId, pipeconf), - "dumpActionProfileGroups-" + actionProfileId.id()); - } - - @Override - public CompletableFuture> dumpActionProfileMembers( - PiActionProfileId actionProfileId, PiPipeconf pipeconf) { - return supplyInContext(() -> doDumpActionProfileMembers(actionProfileId, pipeconf), - "dumpActionProfileMembers-" + actionProfileId.id()); - } - - @Override - public CompletableFuture> removeActionProfileMembers( - PiActionProfileId actionProfileId, - List memberIds, - PiPipeconf pipeconf) { - return supplyInContext( - () -> doRemoveActionProfileMembers(actionProfileId, memberIds, pipeconf), - "cleanupActionProfileMembers-" + actionProfileId.id()); - } - - @Override - public CompletableFuture writeMeterCells(List cellIds, PiPipeconf pipeconf) { - - return supplyInContext(() -> doWriteMeterCells(cellIds, pipeconf), - "writeMeterCells"); - } - - @Override - public CompletableFuture writePreMulticastGroupEntries( - List entries, - WriteOperationType opType) { - return supplyInContext(() -> doWriteMulticastGroupEntries(entries, opType), - "writePreMulticastGroupEntries"); - } - - @Override - public CompletableFuture> readAllMulticastGroupEntries() { - return supplyInContext(this::doReadAllMulticastGroupEntries, - "readAllMulticastGroupEntries"); - } - - @Override - public CompletableFuture> readMeterCells(Set cellIds, - PiPipeconf pipeconf) { - return supplyInContext(() -> doReadMeterCells(Lists.newArrayList(cellIds), pipeconf), - "readMeterCells-" + cellIds.hashCode()); - } - - @Override - public CompletableFuture> readAllMeterCells(Set meterIds, - PiPipeconf pipeconf) { - return supplyInContext(() -> doReadAllMeterCells(Lists.newArrayList(meterIds), pipeconf), - "readAllMeterCells-" + meterIds.hashCode()); - } - - /* Blocking method implementations below */ - - private boolean sendMasterArbitrationUpdate(boolean asMaster) { - BigInteger newId = controller.newMasterElectionId(deviceId); - if (asMaster) { - // Becoming master is a race. Here we increase our chances of win - // against other ONOS nodes in the cluster that are calling start() - // (which is used to start the stream RPC session, not to become - // master). - newId = newId.add(BigInteger.valueOf(1000)); - } - final Uint128 idMsg = bigIntegerToUint128( - controller.newMasterElectionId(deviceId)); - - log.debug("Sending arbitration update to {}... electionId={}", - deviceId, newId); - - streamChannelManager.send( - StreamMessageRequest.newBuilder() - .setArbitration( - MasterArbitrationUpdate - .newBuilder() - .setDeviceId(p4DeviceId) - .setElectionId(idMsg) - .build()) - .build()); - clientElectionId = idMsg; - return true; - } - - private ForwardingPipelineConfig getPipelineConfig( - PiPipeconf pipeconf, ByteBuffer deviceData) { - P4Info p4Info = PipeconfHelper.getP4Info(pipeconf); - if (p4Info == null) { - // Problem logged by PipeconfHelper. - return null; - } - - ForwardingPipelineConfig.Cookie pipeconfCookie = ForwardingPipelineConfig.Cookie - .newBuilder() - .setCookie(pipeconf.fingerprint()) - .build(); - - // FIXME: This is specific to PI P4Runtime implementation. - P4Config.P4DeviceConfig p4DeviceConfigMsg = P4Config.P4DeviceConfig - .newBuilder() - .setExtras(P4Config.P4DeviceConfig.Extras.getDefaultInstance()) - .setReassign(true) - .setDeviceData(ByteString.copyFrom(deviceData)) - .build(); - - return ForwardingPipelineConfig - .newBuilder() - .setP4Info(p4Info) - .setP4DeviceConfig(p4DeviceConfigMsg.toByteString()) - .setCookie(pipeconfCookie) - .build(); - } - - private boolean doIsPipelineConfigSet(PiPipeconf pipeconf, ByteBuffer deviceData) { - - GetForwardingPipelineConfigRequest request = GetForwardingPipelineConfigRequest - .newBuilder() - .setDeviceId(p4DeviceId) - .setResponseType(GetForwardingPipelineConfigRequest - .ResponseType.COOKIE_ONLY) - .build(); - - GetForwardingPipelineConfigResponse resp; - try { - resp = this.blockingStub - .getForwardingPipelineConfig(request); - } catch (StatusRuntimeException ex) { - checkGrpcException(ex); - // FAILED_PRECONDITION means that a pipeline config was not set in - // the first place. Don't bother logging. - if (!ex.getStatus().getCode() - .equals(Status.FAILED_PRECONDITION.getCode())) { - log.warn("Unable to get pipeline config from {}: {}", - deviceId, ex.getMessage()); - } - return false; - } - if (!resp.getConfig().hasCookie()) { - log.warn("{} returned GetForwardingPipelineConfigResponse " + - "with 'cookie' field unset. " + - "Will try by comparing 'device_data'...", - deviceId); - return doIsPipelineConfigSetWithData(pipeconf, deviceData); - } - - return resp.getConfig().getCookie().getCookie() == pipeconf.fingerprint(); - } - - private boolean doIsPipelineConfigSetWithData(PiPipeconf pipeconf, ByteBuffer deviceData) { - - GetForwardingPipelineConfigRequest request = GetForwardingPipelineConfigRequest - .newBuilder() - .setDeviceId(p4DeviceId) - .build(); - - GetForwardingPipelineConfigResponse resp; - try { - resp = this.blockingStub - .getForwardingPipelineConfig(request); - } catch (StatusRuntimeException ex) { - checkGrpcException(ex); - return false; - } - - ForwardingPipelineConfig expectedConfig = getPipelineConfig( - pipeconf, deviceData); - - if (expectedConfig == null) { - return false; - } - if (!resp.hasConfig()) { - log.warn("{} returned GetForwardingPipelineConfigResponse " + - "with 'config' field unset", - deviceId); - return false; - } - if (resp.getConfig().getP4DeviceConfig().isEmpty() - && !expectedConfig.getP4DeviceConfig().isEmpty()) { - // Don't bother with a warn or error since we don't really allow - // updating the pipeline to a different one. So the P4Info should be - // enough for us. - log.debug("{} returned GetForwardingPipelineConfigResponse " + - "with empty 'p4_device_config' field, " + - "equality will be based only on P4Info", - deviceId); - return resp.getConfig().getP4Info().equals( - expectedConfig.getP4Info()); - } else { - return resp.getConfig().getP4DeviceConfig() - .equals(expectedConfig.getP4DeviceConfig()) - && resp.getConfig().getP4Info() - .equals(expectedConfig.getP4Info()); - } - } - - private boolean doSetPipelineConfig(PiPipeconf pipeconf, ByteBuffer deviceData) { - - log.info("Setting pipeline config for {} to {}...", deviceId, pipeconf.id()); - - checkNotNull(deviceData, "deviceData cannot be null"); - - ForwardingPipelineConfig pipelineConfig = getPipelineConfig(pipeconf, deviceData); - - if (pipelineConfig == null) { - // Error logged in getPipelineConfig() - return false; - } - - SetForwardingPipelineConfigRequest request = SetForwardingPipelineConfigRequest - .newBuilder() - .setDeviceId(p4DeviceId) - .setElectionId(clientElectionId) - .setAction(VERIFY_AND_COMMIT) - .setConfig(pipelineConfig) - .build(); - - try { - //noinspection ResultOfMethodCallIgnored - this.blockingStub.setForwardingPipelineConfig(request); - return true; - } catch (StatusRuntimeException ex) { - checkGrpcException(ex); - log.warn("Unable to set pipeline config on {}: {}", deviceId, ex.getMessage()); - return false; - } - } - - private boolean doWriteTableEntries(List piTableEntries, WriteOperationType opType, - PiPipeconf pipeconf) { - if (piTableEntries.size() == 0) { - return true; - } - - List updateMsgs; - try { - updateMsgs = TableEntryEncoder.encode(piTableEntries, pipeconf) - .stream() - .map(tableEntryMsg -> - Update.newBuilder() - .setEntity(Entity.newBuilder() - .setTableEntry(tableEntryMsg) - .build()) - .setType(UPDATE_TYPES.get(opType)) - .build()) - .collect(toList()); - } catch (CodecException e) { - log.error("Unable to encode table entries, aborting {} operation: {}", - opType.name(), e.getMessage()); - return false; - } - - return write(updateMsgs, piTableEntries, opType, "table entry"); - } - - private List doDumpTables( - Set piTableIds, boolean defaultEntries, PiPipeconf pipeconf) { - - log.debug("Dumping tables {} from {} (pipeconf {})...", - piTableIds, deviceId, pipeconf.id()); - - Set tableIds = Sets.newHashSet(); - if (piTableIds == null) { - // Dump all tables. - tableIds.add(0); - } else { - P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - if (browser == null) { - log.error(MISSING_P4INFO_BROWSER, pipeconf); - return Collections.emptyList(); - } - piTableIds.forEach(piTableId -> { - try { - tableIds.add(browser.tables().getByName(piTableId.id()).getPreamble().getId()); - } catch (P4InfoBrowser.NotFoundException e) { - log.warn("Unable to dump table {}: {}", piTableId, e.getMessage()); - } - }); - } - - if (tableIds.isEmpty()) { - return Collections.emptyList(); - } - - final List entities = tableIds.stream() - .map(tableId -> TableEntry.newBuilder() - .setTableId(tableId) - .setIsDefaultAction(defaultEntries) - .setCounterData(P4RuntimeOuterClass.CounterData.getDefaultInstance()) - .build()) - .map(e -> Entity.newBuilder().setTableEntry(e).build()) - .collect(toList()); - - final List tableEntryMsgs = blockingRead(entities, TABLE_ENTRY) - .map(Entity::getTableEntry) - .collect(toList()); - - log.debug("Retrieved {} entries from {} tables on {}...", - tableEntryMsgs.size(), tableIds.size(), deviceId); - - return TableEntryEncoder.decode(tableEntryMsgs, pipeconf); - } - - private boolean doPacketOut(PiPacketOperation packet, PiPipeconf pipeconf) { - try { - //encode the PiPacketOperation into a PacketOut - PacketOut packetOut = PacketIOCodec.encodePacketOut(packet, pipeconf); - - //Build the request - StreamMessageRequest packetOutRequest = StreamMessageRequest - .newBuilder().setPacket(packetOut).build(); - - //Send the request - streamChannelManager.send(packetOutRequest); - - } catch (P4InfoBrowser.NotFoundException e) { - log.error("Cant find expected metadata in p4Info file. {}", e.getMessage()); - log.debug("Exception", e); - return false; - } - return true; - } - - private void doPacketIn(PacketIn packetInMsg) { - - // Retrieve the pipeconf for this client's device. - PiPipeconfService pipeconfService = DefaultServiceDirectory.getService(PiPipeconfService.class); - if (pipeconfService == null) { - throw new IllegalStateException("PiPipeconfService is null. Can't handle packet in."); - } - final PiPipeconf pipeconf; - if (pipeconfService.ofDevice(deviceId).isPresent() && - pipeconfService.getPipeconf(pipeconfService.ofDevice(deviceId).get()).isPresent()) { - pipeconf = pipeconfService.getPipeconf(pipeconfService.ofDevice(deviceId).get()).get(); - } else { - log.warn("Unable to get pipeconf of {}. Can't handle packet in", deviceId); - return; - } - // Decode packet message and post event. - PiPacketOperation packetOperation = PacketIOCodec.decodePacketIn(packetInMsg, pipeconf, deviceId); - PacketInEvent packetInEventSubject = new PacketInEvent(deviceId, packetOperation); - P4RuntimeEvent event = new P4RuntimeEvent(P4RuntimeEvent.Type.PACKET_IN, packetInEventSubject); - log.debug("Received packet in: {}", event); - controller.postEvent(event); - } - - private void doArbitrationResponse(MasterArbitrationUpdate msg) { - // From the spec... - // - Election_id: The stream RPC with the highest election_id is the - // master. Switch populates with the highest election ID it - // has received from all connected controllers. - // - Status: Switch populates this with OK for the client that is the - // master, and with an error status for all other connected clients (at - // every mastership change). - if (!msg.hasElectionId() || !msg.hasStatus()) { - return; - } - final boolean isMaster = - msg.getStatus().getCode() == Status.OK.getCode().value(); - log.debug("Received arbitration update from {}: isMaster={}, electionId={}", - deviceId, isMaster, uint128ToBigInteger(msg.getElectionId())); - controller.postEvent(new P4RuntimeEvent( - P4RuntimeEvent.Type.ARBITRATION_RESPONSE, - new ArbitrationResponse(deviceId, isMaster))); - isClientMaster.set(isMaster); - } - - private List doReadAllCounterCells( - List counterIds, PiPipeconf pipeconf) { - return doReadCounterEntities( - CounterEntryCodec.readAllCellsEntities(counterIds, pipeconf), - pipeconf); - } - - private List doReadCounterCells( - List cellIds, PiPipeconf pipeconf) { - return doReadCounterEntities( - CounterEntryCodec.encodePiCounterCellIds(cellIds, pipeconf), - pipeconf); - } - - private List doReadCounterEntities( - List counterEntities, PiPipeconf pipeconf) { - - final List entities = blockingRead( - counterEntities, COUNTER_ENTRY, DIRECT_COUNTER_ENTRY) - .collect(toList()); - - return CounterEntryCodec.decodeCounterEntities(entities, pipeconf); - } - - private boolean doWriteActionProfileMembers(List members, - WriteOperationType opType, PiPipeconf pipeconf) { - final List actionProfileMembers; - try { - actionProfileMembers = CODECS.actionProfileMember() - .encodeAllOrFail(members, pipeconf); - } catch (CodecException e) { - log.warn("Unable to {} action profile members: {}", - opType.name(), e.getMessage()); - return false; - } - final List updateMsgs = actionProfileMembers.stream() - .map(m -> Update.newBuilder() - .setEntity(Entity.newBuilder() - .setActionProfileMember(m) - .build()) - .setType(UPDATE_TYPES.get(opType)) - .build()) - .collect(toList()); - return write(updateMsgs, members, opType, "action profile member"); - } - - private List 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(MISSING_P4INFO_BROWSER, pipeconf); - return Collections.emptyList(); - } - - final int actionProfileId; - try { - actionProfileId = browser - .actionProfiles() - .getByName(piActionProfileId.id()) - .getPreamble() - .getId(); - } catch (P4InfoBrowser.NotFoundException e) { - log.warn("Unable to dump groups: {}", e.getMessage()); - return Collections.emptyList(); - } - - // Read all groups from the given action profile. - final Entity entityToRead = Entity.newBuilder() - .setActionProfileGroup( - ActionProfileGroup.newBuilder() - .setActionProfileId(actionProfileId) - .build()) - .build(); - final List groupMsgs = blockingRead(entityToRead, ACTION_PROFILE_GROUP) - .map(Entity::getActionProfileGroup) - .collect(toList()); - - log.debug("Retrieved {} groups from action profile {} on {}...", - groupMsgs.size(), piActionProfileId.id(), deviceId); - - return CODECS.actionProfileGroup().decodeAll(groupMsgs, pipeconf); - } - - private List doDumpActionProfileMembers( - PiActionProfileId actionProfileId, PiPipeconf pipeconf) { - - final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - if (browser == null) { - log.error(MISSING_P4INFO_BROWSER, pipeconf); - return Collections.emptyList(); - } - - final int p4ActProfId; - try { - p4ActProfId = browser - .actionProfiles() - .getByName(actionProfileId.id()) - .getPreamble() - .getId(); - } catch (P4InfoBrowser.NotFoundException e) { - log.warn("Unable to dump action profile members: {}", e.getMessage()); - return Collections.emptyList(); - } - - Entity entityToRead = Entity.newBuilder() - .setActionProfileMember( - ActionProfileMember.newBuilder() - .setActionProfileId(p4ActProfId) - .build()) - .build(); - final List memberMsgs = blockingRead(entityToRead, ACTION_PROFILE_MEMBER) - .map(Entity::getActionProfileMember) - .collect(toList()); - - log.debug("Retrieved {} members from action profile {} on {}...", - memberMsgs.size(), actionProfileId.id(), deviceId); - - return CODECS.actionProfileMember().decodeAll(memberMsgs, pipeconf); - } - - private List doRemoveActionProfileMembers( - PiActionProfileId actionProfileId, - List memberIds, - PiPipeconf pipeconf) { - - if (memberIds.isEmpty()) { - return Collections.emptyList(); - } - - final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - if (browser == null) { - log.error(MISSING_P4INFO_BROWSER, 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 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(toList()); - - log.debug("Removing {} members of action profile '{}'...", - memberIds.size(), actionProfileId); - - return writeAndReturnSuccessEntities( - updateMsgs, memberIds, WriteOperationType.DELETE, - "action profile members"); - } - - private boolean doWriteActionProfileGroup( - PiActionProfileGroup group, WriteOperationType opType, PiPipeconf pipeconf) { - final ActionProfileGroup groupMsg; - try { - groupMsg = CODECS.actionProfileGroup().encode(group, pipeconf); - } catch (CodecException e) { - log.warn("Unable to encode group, aborting {} operation: {}", - opType.name(), e.getMessage()); - return false; - } - - final Update updateMsg = Update.newBuilder() - .setEntity(Entity.newBuilder() - .setActionProfileGroup(groupMsg) - .build()) - .setType(UPDATE_TYPES.get(opType)) - .build(); - - return write(singletonList(updateMsg), singletonList(group), - opType, "group"); - } - - private List doReadAllMeterCells( - List meterIds, PiPipeconf pipeconf) { - return doReadMeterEntities(MeterEntryCodec.readAllCellsEntities( - meterIds, pipeconf), pipeconf); - } - - private List doReadMeterCells( - List cellIds, PiPipeconf pipeconf) { - - final List piMeterCellConfigs = cellIds.stream() - .map(cellId -> PiMeterCellConfig.builder() - .withMeterCellId(cellId) - .build()) - .collect(toList()); - - return doReadMeterEntities(MeterEntryCodec.encodePiMeterCellConfigs( - piMeterCellConfigs, pipeconf), pipeconf); - } - - private List doReadMeterEntities( - List entitiesToRead, PiPipeconf pipeconf) { - - final List responseEntities = blockingRead( - entitiesToRead, METER_ENTRY, DIRECT_METER_ENTRY) - .collect(toList()); - - return MeterEntryCodec.decodeMeterEntities(responseEntities, pipeconf); - } - - private boolean doWriteMeterCells(List cellConfigs, PiPipeconf pipeconf) { - - List updateMsgs = MeterEntryCodec.encodePiMeterCellConfigs(cellConfigs, pipeconf) - .stream() - .map(meterEntryMsg -> - Update.newBuilder() - .setEntity(meterEntryMsg) - .setType(UPDATE_TYPES.get(WriteOperationType.MODIFY)) - .build()) - .collect(toList()); - - if (updateMsgs.size() == 0) { - return true; - } - - return write(updateMsgs, cellConfigs, WriteOperationType.MODIFY, "meter cell config"); - } - - private boolean doWriteMulticastGroupEntries( - List entries, - WriteOperationType opType) { - - final List updateMsgs = entries.stream() - .map(piEntry -> { - try { - return MulticastGroupEntryCodec.encode(piEntry); - } catch (CodecException e) { - log.warn("Unable to encode PiMulticastGroupEntry: {}", e.getMessage()); - return null; - } - }) - .filter(Objects::nonNull) - .map(mcMsg -> PacketReplicationEngineEntry.newBuilder() - .setMulticastGroupEntry(mcMsg) - .build()) - .map(preMsg -> Entity.newBuilder() - .setPacketReplicationEngineEntry(preMsg) - .build()) - .map(entityMsg -> Update.newBuilder() - .setEntity(entityMsg) - .setType(UPDATE_TYPES.get(opType)) - .build()) - .collect(toList()); - return write(updateMsgs, entries, opType, "multicast group entry"); - } - - private List doReadAllMulticastGroupEntries() { - - final Entity entity = Entity.newBuilder() - .setPacketReplicationEngineEntry( - PacketReplicationEngineEntry.newBuilder() - .setMulticastGroupEntry( - MulticastGroupEntry.newBuilder() - .build()) - .build()) - .build(); - - final List mcEntries = blockingRead(entity, PACKET_REPLICATION_ENGINE_ENTRY) - .map(Entity::getPacketReplicationEngineEntry) - .filter(e -> e.getTypeCase().equals(MULTICAST_GROUP_ENTRY)) - .map(PacketReplicationEngineEntry::getMulticastGroupEntry) - .map(MulticastGroupEntryCodec::decode) - .collect(toList()); - - log.debug("Retrieved {} multicast group entries from {}...", - mcEntries.size(), deviceId); - - return mcEntries; - } - - private boolean write(List updates, - List writeEntities, - WriteOperationType opType, - String entryType) { - // True if all entities were successfully written. - return writeAndReturnSuccessEntities(updates, writeEntities, opType, entryType) - .size() == writeEntities.size(); - } - - private List writeAndReturnSuccessEntities( - List updates, List writeEntities, - WriteOperationType opType, String entryType) { - if (updates.isEmpty()) { - return Collections.emptyList(); - } - if (updates.size() != writeEntities.size()) { - log.error("Cannot perform {} operation, provided {} " + - "update messages for {} {} - BUG?", - opType, updates.size(), writeEntities.size(), entryType); - return Collections.emptyList(); - } - try { - //noinspection ResultOfMethodCallIgnored - blockingStub.write(writeRequest(updates)); - return writeEntities; - } catch (StatusRuntimeException e) { - return checkAndLogWriteErrors(writeEntities, e, opType, entryType); - } - } - - private WriteRequest writeRequest(Iterable updateMsgs) { - return WriteRequest.newBuilder() - .setDeviceId(p4DeviceId) - .setElectionId(clientElectionId) - .addAllUpdates(updateMsgs) - .build(); - } - - private Stream blockingRead(Entity entity, Entity.EntityCase entityCase) { - return blockingRead(singletonList(entity), entityCase); - } - - private Stream blockingRead(Iterable entities, - Entity.EntityCase... entityCases) { - // Build read request making sure we are reading what declared. - final ReadRequest.Builder reqBuilder = ReadRequest.newBuilder() - .setDeviceId(p4DeviceId); - final Set entityCaseSet = Sets.newHashSet(entityCases); - for (Entity e : entities) { - checkArgument(entityCaseSet.contains(e.getEntityCase()), - "Entity case mismatch"); - reqBuilder.addEntities(e); - } - final ReadRequest readRequest = reqBuilder.build(); - if (readRequest.getEntitiesCount() == 0) { - return Stream.empty(); - } - // Issue read. - final Iterator responseIterator; - try { - responseIterator = blockingStub.read(readRequest); - } catch (StatusRuntimeException e) { - checkGrpcException(e); - final String caseString = entityCaseSet.stream() - .map(Entity.EntityCase::name) - .collect(joining("/")); - log.warn("Unable to read {} from {}: {}", - caseString, deviceId, e.getMessage()); - log.debug("Exception during read", e); - return Stream.empty(); - } - // Filter results. - return Tools.stream(() -> responseIterator) - .map(ReadResponse::getEntitiesList) - .flatMap(List::stream) - .filter(e -> entityCaseSet.contains(e.getEntityCase())); - } - - protected Void doShutdown() { - streamChannelManager.complete(); - return super.doShutdown(); - } - - // Returns the collection of succesfully write entities. - private List checkAndLogWriteErrors( - List writeEntities, StatusRuntimeException ex, - WriteOperationType opType, String entryType) { - - checkGrpcException(ex); - - final List errors = extractWriteErrorDetails(ex); - - if (errors.isEmpty()) { - final String description = ex.getStatus().getDescription(); - log.warn("Unable to {} {} {}(s) on {}: {}", - opType.name(), writeEntities.size(), entryType, deviceId, - ex.getStatus().getCode().name(), - description == null ? "" : " - " + description); - return Collections.emptyList(); - } - - if (errors.size() == writeEntities.size()) { - List okEntities = Lists.newArrayList(); - Iterator 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()); - 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(); - } - } - - private List extractWriteErrorDetails( - StatusRuntimeException ex) { - if (!ex.getTrailers().containsKey(STATUS_DETAILS_KEY)) { - return Collections.emptyList(); - } - com.google.rpc.Status status = ex.getTrailers().get(STATUS_DETAILS_KEY); - if (status == null) { - return Collections.emptyList(); - } - return status.getDetailsList().stream() - .map(any -> { - try { - return any.unpack(P4RuntimeOuterClass.Error.class); - } catch (InvalidProtocolBufferException e) { - log.warn("Unable to unpack P4Runtime Error: {}", - any.toString()); - return null; - } - }) - .filter(Objects::nonNull) - .collect(toList()); - } - - private String parseP4Error(P4RuntimeOuterClass.Error err) { - return format("%s %s%s (%s:%d)", - Status.fromCodeValue(err.getCanonicalCode()).getCode(), - err.getMessage(), - err.hasDetails() ? ", " + err.getDetails().toString() : "", - err.getSpace(), - err.getCode()); - } - - private void checkGrpcException(StatusRuntimeException ex) { - switch (ex.getStatus().getCode()) { - case OK: - break; - case CANCELLED: - break; - case UNKNOWN: - break; - case INVALID_ARGUMENT: - break; - case DEADLINE_EXCEEDED: - break; - case NOT_FOUND: - break; - case ALREADY_EXISTS: - break; - case PERMISSION_DENIED: - // Notify upper layers that this node is not master. - controller.postEvent(new P4RuntimeEvent( - P4RuntimeEvent.Type.PERMISSION_DENIED, - new BaseP4RuntimeEventSubject(deviceId))); - break; - case RESOURCE_EXHAUSTED: - break; - case FAILED_PRECONDITION: - break; - case ABORTED: - break; - case OUT_OF_RANGE: - break; - case UNIMPLEMENTED: - break; - case INTERNAL: - break; - case UNAVAILABLE: - // Channel might be closed. - controller.postEvent(new P4RuntimeEvent( - P4RuntimeEvent.Type.CHANNEL_EVENT, - new ChannelEvent(deviceId, ChannelEvent.Type.ERROR))); - break; - case DATA_LOSS: - break; - case UNAUTHENTICATED: - break; - default: - break; - } - } - - private Uint128 bigIntegerToUint128(BigInteger value) { - final byte[] arr = value.toByteArray(); - final ByteBuffer bb = ByteBuffer.allocate(Long.BYTES * 2) - .put(new byte[Long.BYTES * 2 - arr.length]) - .put(arr); - bb.rewind(); - return Uint128.newBuilder() - .setHigh(bb.getLong()) - .setLow(bb.getLong()) - .build(); - } - - private BigInteger uint128ToBigInteger(Uint128 value) { - return new BigInteger( - ByteBuffer.allocate(Long.BYTES * 2) - .putLong(value.getHigh()) - .putLong(value.getLow()) - .array()); - } - - /** - * A manager for the P4Runtime stream channel that opportunistically creates - * new stream RCP stubs (e.g. when one fails because of errors) and posts - * channel events via the P4Runtime controller. - */ - private final class StreamChannelManager { - - private final ManagedChannel channel; - private final AtomicBoolean open; - private final StreamObserver responseObserver; - private ClientCallStreamObserver requestObserver; - - private StreamChannelManager(ManagedChannel channel) { - this.channel = channel; - this.responseObserver = new InternalStreamResponseObserver(this); - this.open = new AtomicBoolean(false); - } - - private void initIfRequired() { - if (requestObserver == null) { - log.debug("Creating new stream channel for {}...", deviceId); - requestObserver = - (ClientCallStreamObserver) - P4RuntimeGrpc.newStub(channel) - .streamChannel(responseObserver); - open.set(false); - } - } - - public boolean send(StreamMessageRequest value) { - synchronized (this) { - initIfRequired(); - try { - requestObserver.onNext(value); - // FIXME - // signalOpen(); - return true; - } catch (Throwable ex) { - if (ex instanceof StatusRuntimeException) { - log.warn("Unable to send {} to {}: {}", - value.getUpdateCase().toString(), deviceId, ex.getMessage()); - } else { - log.warn(format( - "Exception while sending %s to %s", - value.getUpdateCase().toString(), deviceId), ex); - } - complete(); - return false; - } - } - } - - public void complete() { - synchronized (this) { - signalClosed(); - if (requestObserver != null) { - requestObserver.onCompleted(); - requestObserver.cancel("Terminated", null); - requestObserver = null; - } - } - } - - void signalOpen() { - synchronized (this) { - final boolean wasOpen = open.getAndSet(true); - if (!wasOpen) { - controller.postEvent(new P4RuntimeEvent( - P4RuntimeEvent.Type.CHANNEL_EVENT, - new ChannelEvent(deviceId, ChannelEvent.Type.OPEN))); - } - } - } - - void signalClosed() { - synchronized (this) { - final boolean wasOpen = open.getAndSet(false); - if (wasOpen) { - controller.postEvent(new P4RuntimeEvent( - P4RuntimeEvent.Type.CHANNEL_EVENT, - new ChannelEvent(deviceId, ChannelEvent.Type.CLOSED))); - } - } - } - - public boolean isOpen() { - return open.get(); - } - } - - /** - * Handles messages received from the device on the stream channel. - */ - private final class InternalStreamResponseObserver - implements StreamObserver { - - private final StreamChannelManager streamChannelManager; - - private InternalStreamResponseObserver( - StreamChannelManager streamChannelManager) { - this.streamChannelManager = streamChannelManager; - } - - @Override - public void onNext(StreamMessageResponse message) { - streamChannelManager.signalOpen(); - executorService.submit(() -> doNext(message)); - } - - private void doNext(StreamMessageResponse message) { - try { - log.debug("Received message on stream channel from {}: {}", - deviceId, message.getUpdateCase()); - switch (message.getUpdateCase()) { - case PACKET: - doPacketIn(message.getPacket()); - return; - case ARBITRATION: - doArbitrationResponse(message.getArbitration()); - return; - default: - log.warn("Unrecognized stream message from {}: {}", - deviceId, message.getUpdateCase()); - } - } catch (Throwable ex) { - log.error("Exception while processing stream message from {}", - deviceId, ex); - } - } - - @Override - public void onError(Throwable throwable) { - if (throwable instanceof StatusRuntimeException) { - StatusRuntimeException sre = (StatusRuntimeException) throwable; - if (sre.getStatus().getCause() instanceof ConnectException) { - log.warn("Device {} is unreachable ({})", - deviceId, sre.getCause().getMessage()); - } else { - log.warn("Received error on stream channel for {}: {}", - deviceId, throwable.getMessage()); - } - } else { - log.warn(format("Received exception on stream channel for %s", - deviceId), throwable); - } - streamChannelManager.complete(); - } - - @Override - public void onCompleted() { - log.warn("Stream channel for {} has completed", deviceId); - streamChannelManager.complete(); - } - } -} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeCodecs.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeCodecs.java deleted file mode 100644 index 1126fef21f..0000000000 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeCodecs.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2019-present Open Networking Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.onosproject.p4runtime.ctl; - -/** - * Utility class that provides access to P4Runtime codec instances. - */ -final class P4RuntimeCodecs { - - static final P4RuntimeCodecs CODECS = new P4RuntimeCodecs(); - - private final ActionProfileMemberCodec actionProfileMember; - private final ActionProfileGroupCodec actionProfileGroup; - - private P4RuntimeCodecs() { - this.actionProfileMember = new ActionProfileMemberCodec(); - this.actionProfileGroup = new ActionProfileGroupCodec(); - } - - ActionProfileMemberCodec actionProfileMember() { - return actionProfileMember; - } - - ActionProfileGroupCodec actionProfileGroup() { - return actionProfileGroup; - } -} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java deleted file mode 100644 index 726e09258f..0000000000 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2017-present Open Networking Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.onosproject.p4runtime.ctl; - -import com.google.protobuf.ByteString; -import org.onlab.util.ImmutableByteSequence; -import org.onosproject.net.DeviceId; -import org.onosproject.net.pi.model.PiControlMetadataId; -import org.onosproject.net.pi.model.PiPacketOperationType; -import org.onosproject.net.pi.model.PiPipeconf; -import org.onosproject.net.pi.runtime.PiControlMetadata; -import org.onosproject.net.pi.runtime.PiPacketOperation; -import org.slf4j.Logger; -import p4.config.v1.P4InfoOuterClass; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static org.onlab.util.ImmutableByteSequence.copyFrom; -import static org.onosproject.p4runtime.ctl.P4InfoBrowser.NotFoundException; -import static org.slf4j.LoggerFactory.getLogger; -import static p4.v1.P4RuntimeOuterClass.PacketIn; -import static p4.v1.P4RuntimeOuterClass.PacketMetadata; -import static p4.v1.P4RuntimeOuterClass.PacketOut; - -/** - * Encoder of packet metadata, from ONOS Pi* format, to P4Runtime protobuf messages, and vice versa. - */ -final class PacketIOCodec { - - private static final Logger log = getLogger(PacketIOCodec.class); - - private static final String PACKET_OUT = "packet_out"; - - private static final String PACKET_IN = "packet_in"; - - // TODO: implement cache of encoded entities. - - private PacketIOCodec() { - // hide. - } - - /** - * Returns a P4Runtime packet out protobuf message, encoded from the given PiPacketOperation for the given pipeconf. - * If a PI packet metadata inside the PacketOperation cannot be encoded, it is skipped, hence the returned PacketOut - * collection of metadatas might have different size than the input one. - *

- * Please check the log for an explanation of any error that might have occurred. - * - * @param packet PI packet operation - * @param pipeconf the pipeconf for the program on the switch - * @return a P4Runtime packet out protobuf message - * @throws NotFoundException if the browser can't find the packet_out in the given p4Info - */ - static PacketOut encodePacketOut(PiPacketOperation packet, PiPipeconf pipeconf) - throws NotFoundException { - - //Get the P4browser - P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - - //Get the packet out controller packet metadata - P4InfoOuterClass.ControllerPacketMetadata controllerControlMetadata = - browser.controllerPacketMetadatas().getByName(PACKET_OUT); - PacketOut.Builder packetOutBuilder = PacketOut.newBuilder(); - - //outer controller packet metadata id - int controllerControlMetadataId = controllerControlMetadata.getPreamble().getId(); - - //Add all its metadata to the packet out - packetOutBuilder.addAllMetadata(encodeControlMetadata(packet, browser, controllerControlMetadataId)); - - //Set the packet out payload - packetOutBuilder.setPayload(ByteString.copyFrom(packet.data().asReadOnlyBuffer())); - return packetOutBuilder.build(); - - } - - private static List encodeControlMetadata(PiPacketOperation packet, - P4InfoBrowser browser, int controllerControlMetadataId) { - return packet.metadatas().stream().map(metadata -> { - try { - //get each metadata id - int metadataId = browser.packetMetadatas(controllerControlMetadataId) - .getByName(metadata.id().toString()).getId(); - - //Add the metadata id and it's data the packet out - return PacketMetadata.newBuilder() - .setMetadataId(metadataId) - .setValue(ByteString.copyFrom(metadata.value().asReadOnlyBuffer())) - .build(); - } catch (NotFoundException e) { - log.error("Cant find metadata with name {} in p4Info file.", metadata.id()); - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toList()); - } - - /** - * Returns a PiPacketOperation, decoded from the given P4Runtime PacketIn protobuf message for the given pipeconf - * and device ID. If a PI packet metadata inside the protobuf message cannot be decoded, it is skipped, hence the - * returned PiPacketOperation collection of metadatas might have different size than the input one. - *

- * Please check the log for an explanation of any error that might have occurred. - * - * @param packetIn the P4Runtime PacketIn message - * @param pipeconf the pipeconf for the program on the switch - * @param deviceId the deviceId that originated the PacketIn message - * @return a PiPacketOperation - */ - static PiPacketOperation decodePacketIn(PacketIn packetIn, PiPipeconf pipeconf, DeviceId deviceId) { - - //Get the P4browser - P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - - List packetMetadatas; - try { - int controllerControlMetadataId = browser.controllerPacketMetadatas().getByName(PACKET_IN) - .getPreamble().getId(); - packetMetadatas = decodeControlMetadataIn(packetIn.getMetadataList(), browser, - controllerControlMetadataId); - } catch (NotFoundException e) { - log.error("Unable to decode packet metadatas: {}", e.getMessage()); - packetMetadatas = Collections.emptyList(); - } - - //Transform the packetIn data - ImmutableByteSequence data = copyFrom(packetIn.getPayload().asReadOnlyByteBuffer()); - - //Build the PiPacketOperation with all the metadatas. - return PiPacketOperation.builder() - .forDevice(deviceId) - .withType(PiPacketOperationType.PACKET_IN) - .withMetadatas(packetMetadatas) - .withData(data) - .build(); - } - - private static List decodeControlMetadataIn(List packetMetadatas, - P4InfoBrowser browser, - int controllerControlMetadataId) { - return packetMetadatas.stream().map(packetMetadata -> { - try { - - int packetMetadataId = packetMetadata.getMetadataId(); - String packetMetadataName = browser.packetMetadatas(controllerControlMetadataId) - .getById(packetMetadataId).getName(); - - PiControlMetadataId metadataId = PiControlMetadataId.of(packetMetadataName); - - //Build each metadata. - return PiControlMetadata.builder() - .withId(metadataId) - .withValue(ImmutableByteSequence.copyFrom(packetMetadata.getValue().asReadOnlyByteBuffer())) - .build(); - } catch (NotFoundException e) { - log.error("Cant find metadata with id {} in p4Info file.", packetMetadata.getMetadataId()); - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toList()); - } - -} 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 deleted file mode 100644 index 357e41d517..0000000000 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Copyright 2017-present Open Networking Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.onosproject.p4runtime.ctl; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.protobuf.ByteString; -import org.onlab.util.ImmutableByteSequence; -import org.onosproject.net.pi.model.PiActionId; -import org.onosproject.net.pi.model.PiActionParamId; -import org.onosproject.net.pi.model.PiMatchFieldId; -import org.onosproject.net.pi.model.PiPipeconf; -import org.onosproject.net.pi.model.PiTableId; -import org.onosproject.net.pi.runtime.PiAction; -import org.onosproject.net.pi.runtime.PiActionParam; -import org.onosproject.net.pi.runtime.PiActionProfileGroupId; -import org.onosproject.net.pi.runtime.PiActionProfileMemberId; -import org.onosproject.net.pi.runtime.PiCounterCellData; -import org.onosproject.net.pi.runtime.PiExactFieldMatch; -import org.onosproject.net.pi.runtime.PiFieldMatch; -import org.onosproject.net.pi.runtime.PiLpmFieldMatch; -import org.onosproject.net.pi.runtime.PiMatchKey; -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.slf4j.Logger; -import p4.config.v1.P4InfoOuterClass; -import p4.v1.P4RuntimeOuterClass.Action; -import p4.v1.P4RuntimeOuterClass.CounterData; -import p4.v1.P4RuntimeOuterClass.FieldMatch; -import p4.v1.P4RuntimeOuterClass.TableAction; -import p4.v1.P4RuntimeOuterClass.TableEntry; - -import java.util.Collections; -import java.util.List; - -import static com.google.common.base.Preconditions.checkNotNull; -import static java.lang.String.format; -import static org.onlab.util.ImmutableByteSequence.copyFrom; -import static org.onosproject.p4runtime.ctl.P4RuntimeUtils.assertPrefixLen; -import static org.onosproject.p4runtime.ctl.P4RuntimeUtils.assertSize; -import static org.slf4j.LoggerFactory.getLogger; - -/** - * Encoder/Decoder of table entries, from ONOS Pi* format, to P4Runtime protobuf messages, and vice versa. - */ -final class TableEntryEncoder { - private static final Logger log = getLogger(TableEntryEncoder.class); - - private static final String VALUE_OF_PREFIX = "value of "; - private static final String MASK_OF_PREFIX = "mask of "; - private static final String HIGH_RANGE_VALUE_OF_PREFIX = "high range value of "; - private static final String LOW_RANGE_VALUE_OF_PREFIX = "low range value of "; - - // TODO: implement cache of encoded entities. - - private TableEntryEncoder() { - // hide. - } - - /** - * Returns a collection of P4Runtime table entry protobuf messages, encoded - * from the given collection of PI table entries for the given pipeconf. If - * a PI table entry cannot be encoded, an EncodeException is thrown. - * - * @param piTableEntries PI table entries - * @param pipeconf PI pipeconf - * @return collection of P4Runtime table entry protobuf messages - * @throws CodecException if a PI table entry cannot be encoded - */ - static List encode(List piTableEntries, - PiPipeconf pipeconf) - throws CodecException { - - P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - - if (browser == null) { - throw new CodecException(format( - "Unable to get a P4Info browser for pipeconf %s", pipeconf.id())); - } - - ImmutableList.Builder tableEntryMsgListBuilder = ImmutableList.builder(); - - for (PiTableEntry piTableEntry : piTableEntries) { - try { - tableEntryMsgListBuilder.add(encodePiTableEntry(piTableEntry, browser)); - } catch (P4InfoBrowser.NotFoundException e) { - throw new CodecException(e.getMessage()); - } - } - - return tableEntryMsgListBuilder.build(); - } - - /** - * Same as {@link #encode(List, PiPipeconf)} but encodes only one entry. - * - * @param piTableEntry table entry - * @param pipeconf pipeconf - * @return encoded table entry message - * @throws CodecException if entry cannot be encoded - * @throws P4InfoBrowser.NotFoundException if the required information cannot be find in the pipeconf's P4info - */ - static TableEntry encode(PiTableEntry piTableEntry, PiPipeconf pipeconf) - throws CodecException, P4InfoBrowser.NotFoundException { - - P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - if (browser == null) { - throw new CodecException(format("Unable to get a P4Info browser for pipeconf %s", pipeconf.id())); - } - - return encodePiTableEntry(piTableEntry, browser); - } - - /** - * Returns a collection of PI table entry objects, decoded from the given collection of P4Runtime table entry - * messages for the given pipeconf. If a table entry message cannot be decoded, it is skipped, hence the returned - * collection might have different size than the input one. - *

- * Please check the log for an explanation of any error that might have occurred. - * - * @param tableEntryMsgs P4Runtime table entry messages - * @param pipeconf PI pipeconf - * @return collection of PI table entry objects - */ - static List decode(List tableEntryMsgs, PiPipeconf pipeconf) { - - P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - - if (browser == null) { - log.error("Unable to get a P4Info browser for pipeconf {}, skipping decoding of all table entries"); - return Collections.emptyList(); - } - - ImmutableList.Builder piTableEntryListBuilder = ImmutableList.builder(); - - for (TableEntry tableEntryMsg : tableEntryMsgs) { - try { - piTableEntryListBuilder.add(decodeTableEntryMsg(tableEntryMsg, browser)); - } catch (P4InfoBrowser.NotFoundException | CodecException e) { - log.error("Unable to decode table entry message: {}", e.getMessage()); - } - } - - return piTableEntryListBuilder.build(); - } - - /** - * Same as {@link #decode(List, PiPipeconf)} but decodes only one entry. - * - * @param tableEntryMsg table entry message - * @param pipeconf pipeconf - * @return decoded PI table entry - * @throws CodecException if message cannot be decoded - * @throws P4InfoBrowser.NotFoundException if the required information cannot be find in the pipeconf's P4info - */ - static PiTableEntry decode(TableEntry tableEntryMsg, PiPipeconf pipeconf) - throws CodecException, P4InfoBrowser.NotFoundException { - - P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - if (browser == null) { - throw new CodecException(format("Unable to get a P4Info browser for pipeconf %s", pipeconf.id())); - } - return decodeTableEntryMsg(tableEntryMsg, browser); - } - - /** - * Returns a table entry protobuf message, encoded from the given table id and match key, for the given pipeconf. - * The returned table entry message can be only used to reference an existing entry, i.e. a read operation, and not - * a write one wince it misses other fields (action, priority, etc.). - * - * @param tableId table identifier - * @param matchKey match key - * @param pipeconf pipeconf - * @return table entry message - * @throws CodecException if message cannot be encoded - * @throws P4InfoBrowser.NotFoundException if the required information cannot be find in the pipeconf's P4info - */ - static TableEntry encode(PiTableId tableId, PiMatchKey matchKey, PiPipeconf pipeconf) - throws CodecException, P4InfoBrowser.NotFoundException { - - P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - TableEntry.Builder tableEntryMsgBuilder = TableEntry.newBuilder(); - - P4InfoOuterClass.Table tableInfo = browser.tables().getByName(tableId.id()); - - // Table id. - tableEntryMsgBuilder.setTableId(tableInfo.getPreamble().getId()); - - // Field matches. - if (matchKey.equals(PiMatchKey.EMPTY)) { - tableEntryMsgBuilder.setIsDefaultAction(true); - } else { - for (PiFieldMatch piFieldMatch : matchKey.fieldMatches()) { - tableEntryMsgBuilder.addMatch(encodePiFieldMatch(piFieldMatch, tableInfo, browser)); - } - } - - return tableEntryMsgBuilder.build(); - } - - private static TableEntry encodePiTableEntry(PiTableEntry piTableEntry, P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - - TableEntry.Builder tableEntryMsgBuilder = TableEntry.newBuilder(); - - P4InfoOuterClass.Table tableInfo = browser.tables().getByName(piTableEntry.table().id()); - - // Table id. - tableEntryMsgBuilder.setTableId(tableInfo.getPreamble().getId()); - - // Priority. - // FIXME: check on P4Runtime if/what is the default priority. - piTableEntry.priority().ifPresent(tableEntryMsgBuilder::setPriority); - - // Controller metadata (cookie) - tableEntryMsgBuilder.setControllerMetadata(piTableEntry.cookie()); - - // Timeout. - if (piTableEntry.timeout().isPresent()) { - log.warn("Found PI table entry with timeout set, not supported in P4Runtime: {}", piTableEntry); - } - - // Table action. - if (piTableEntry.action() != null) { - tableEntryMsgBuilder.setAction(encodePiTableAction(piTableEntry.action(), browser)); - } - - // Field matches. - if (piTableEntry.matchKey().equals(PiMatchKey.EMPTY)) { - tableEntryMsgBuilder.setIsDefaultAction(true); - } else { - for (PiFieldMatch piFieldMatch : piTableEntry.matchKey().fieldMatches()) { - tableEntryMsgBuilder.addMatch(encodePiFieldMatch(piFieldMatch, tableInfo, browser)); - } - } - - // Counter. - if (piTableEntry.counter() != null) { - tableEntryMsgBuilder.setCounterData(encodeCounter(piTableEntry.counter())); - } - - return tableEntryMsgBuilder.build(); - } - - private static PiTableEntry decodeTableEntryMsg(TableEntry tableEntryMsg, P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - - PiTableEntry.Builder piTableEntryBuilder = PiTableEntry.builder(); - - P4InfoOuterClass.Table tableInfo = browser.tables().getById(tableEntryMsg.getTableId()); - - // Table id. - piTableEntryBuilder.forTable(PiTableId.of(tableInfo.getPreamble().getName())); - - // Priority. - if (tableEntryMsg.getPriority() > 0) { - piTableEntryBuilder.withPriority(tableEntryMsg.getPriority()); - } - - // Controller metadata (cookie) - piTableEntryBuilder.withCookie(tableEntryMsg.getControllerMetadata()); - - // Table action. - if (tableEntryMsg.hasAction()) { - piTableEntryBuilder.withAction(decodeTableActionMsg(tableEntryMsg.getAction(), browser)); - } - - // Timeout. - // FIXME: how to decode table entry messages with timeout, given that the timeout value is lost after encoding? - - // Match key for field matches. - piTableEntryBuilder.withMatchKey(decodeFieldMatchMsgs(tableEntryMsg.getMatchList(), tableInfo, browser)); - - // Counter. - piTableEntryBuilder.withCounterCellData(decodeCounter(tableEntryMsg.getCounterData())); - - return piTableEntryBuilder.build(); - } - - private static FieldMatch encodePiFieldMatch(PiFieldMatch piFieldMatch, P4InfoOuterClass.Table tableInfo, - P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - - FieldMatch.Builder fieldMatchMsgBuilder = FieldMatch.newBuilder(); - - // FIXME: check how field names for stacked headers are constructed in P4Runtime. - String fieldName = piFieldMatch.fieldId().id(); - int tableId = tableInfo.getPreamble().getId(); - P4InfoOuterClass.MatchField matchFieldInfo = browser.matchFields(tableId).getByName(fieldName); - String entityName = format("field match '%s' of table '%s'", - matchFieldInfo.getName(), tableInfo.getPreamble().getName()); - int fieldId = matchFieldInfo.getId(); - int fieldBitwidth = matchFieldInfo.getBitwidth(); - - fieldMatchMsgBuilder.setFieldId(fieldId); - - switch (piFieldMatch.type()) { - case EXACT: - PiExactFieldMatch fieldMatch = (PiExactFieldMatch) piFieldMatch; - ByteString exactValue = ByteString.copyFrom(fieldMatch.value().asReadOnlyBuffer()); - assertSize(VALUE_OF_PREFIX + entityName, exactValue, fieldBitwidth); - return fieldMatchMsgBuilder.setExact( - FieldMatch.Exact - .newBuilder() - .setValue(exactValue) - .build()) - .build(); - case TERNARY: - PiTernaryFieldMatch ternaryMatch = (PiTernaryFieldMatch) piFieldMatch; - ByteString ternaryValue = ByteString.copyFrom(ternaryMatch.value().asReadOnlyBuffer()); - ByteString ternaryMask = ByteString.copyFrom(ternaryMatch.mask().asReadOnlyBuffer()); - assertSize(VALUE_OF_PREFIX + entityName, ternaryValue, fieldBitwidth); - assertSize(MASK_OF_PREFIX + entityName, ternaryMask, fieldBitwidth); - return fieldMatchMsgBuilder.setTernary( - FieldMatch.Ternary - .newBuilder() - .setValue(ternaryValue) - .setMask(ternaryMask) - .build()) - .build(); - case LPM: - PiLpmFieldMatch lpmMatch = (PiLpmFieldMatch) piFieldMatch; - ByteString lpmValue = ByteString.copyFrom(lpmMatch.value().asReadOnlyBuffer()); - int lpmPrefixLen = lpmMatch.prefixLength(); - assertSize(VALUE_OF_PREFIX + entityName, lpmValue, fieldBitwidth); - assertPrefixLen(entityName, lpmPrefixLen, fieldBitwidth); - return fieldMatchMsgBuilder.setLpm( - FieldMatch.LPM.newBuilder() - .setValue(lpmValue) - .setPrefixLen(lpmPrefixLen) - .build()) - .build(); - case RANGE: - PiRangeFieldMatch rangeMatch = (PiRangeFieldMatch) piFieldMatch; - ByteString rangeHighValue = ByteString.copyFrom(rangeMatch.highValue().asReadOnlyBuffer()); - ByteString rangeLowValue = ByteString.copyFrom(rangeMatch.lowValue().asReadOnlyBuffer()); - assertSize(HIGH_RANGE_VALUE_OF_PREFIX + entityName, rangeHighValue, fieldBitwidth); - assertSize(LOW_RANGE_VALUE_OF_PREFIX + entityName, rangeLowValue, fieldBitwidth); - return fieldMatchMsgBuilder.setRange( - FieldMatch.Range.newBuilder() - .setHigh(rangeHighValue) - .setLow(rangeLowValue) - .build()) - .build(); - default: - throw new CodecException(format( - "Building of match type %s not implemented", piFieldMatch.type())); - } - } - - /** - * Returns a PI match key, decoded from the given table entry protobuf message, for the given pipeconf. - * - * @param tableEntryMsg table entry message - * @param pipeconf pipeconf - * @return PI match key - * @throws CodecException if message cannot be decoded - * @throws P4InfoBrowser.NotFoundException if the required information cannot be find in the pipeconf's P4info - */ - static PiMatchKey decodeMatchKey(TableEntry tableEntryMsg, PiPipeconf pipeconf) - throws P4InfoBrowser.NotFoundException, CodecException { - P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); - P4InfoOuterClass.Table tableInfo = browser.tables().getById(tableEntryMsg.getTableId()); - if (tableEntryMsg.getMatchCount() == 0) { - return PiMatchKey.EMPTY; - } else { - return decodeFieldMatchMsgs(tableEntryMsg.getMatchList(), tableInfo, browser); - } - } - - private static PiMatchKey decodeFieldMatchMsgs(List fieldMatchs, P4InfoOuterClass.Table tableInfo, - P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - // Match key for field matches. - PiMatchKey.Builder piMatchKeyBuilder = PiMatchKey.builder(); - for (FieldMatch fieldMatchMsg : fieldMatchs) { - piMatchKeyBuilder.addFieldMatch(decodeFieldMatchMsg(fieldMatchMsg, tableInfo, browser)); - } - return piMatchKeyBuilder.build(); - } - - private static PiFieldMatch decodeFieldMatchMsg(FieldMatch fieldMatchMsg, P4InfoOuterClass.Table tableInfo, - P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - - int tableId = tableInfo.getPreamble().getId(); - String fieldMatchName = browser.matchFields(tableId).getById(fieldMatchMsg.getFieldId()).getName(); - PiMatchFieldId headerFieldId = PiMatchFieldId.of(fieldMatchName); - - FieldMatch.FieldMatchTypeCase typeCase = fieldMatchMsg.getFieldMatchTypeCase(); - - switch (typeCase) { - case EXACT: - FieldMatch.Exact exactFieldMatch = fieldMatchMsg.getExact(); - ImmutableByteSequence exactValue = copyFrom(exactFieldMatch.getValue().asReadOnlyByteBuffer()); - return new PiExactFieldMatch(headerFieldId, exactValue); - case TERNARY: - FieldMatch.Ternary ternaryFieldMatch = fieldMatchMsg.getTernary(); - ImmutableByteSequence ternaryValue = copyFrom(ternaryFieldMatch.getValue().asReadOnlyByteBuffer()); - ImmutableByteSequence ternaryMask = copyFrom(ternaryFieldMatch.getMask().asReadOnlyByteBuffer()); - return new PiTernaryFieldMatch(headerFieldId, ternaryValue, ternaryMask); - case LPM: - FieldMatch.LPM lpmFieldMatch = fieldMatchMsg.getLpm(); - ImmutableByteSequence lpmValue = copyFrom(lpmFieldMatch.getValue().asReadOnlyByteBuffer()); - int lpmPrefixLen = lpmFieldMatch.getPrefixLen(); - return new PiLpmFieldMatch(headerFieldId, lpmValue, lpmPrefixLen); - case RANGE: - FieldMatch.Range rangeFieldMatch = fieldMatchMsg.getRange(); - ImmutableByteSequence rangeHighValue = copyFrom(rangeFieldMatch.getHigh().asReadOnlyByteBuffer()); - ImmutableByteSequence rangeLowValue = copyFrom(rangeFieldMatch.getLow().asReadOnlyByteBuffer()); - return new PiRangeFieldMatch(headerFieldId, rangeLowValue, rangeHighValue); - default: - throw new CodecException(format( - "Decoding of field match type '%s' not implemented", typeCase.name())); - } - } - - static TableAction encodePiTableAction(PiTableAction piTableAction, P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - checkNotNull(piTableAction, "Cannot encode null PiTableAction"); - TableAction.Builder tableActionMsgBuilder = TableAction.newBuilder(); - - switch (piTableAction.type()) { - case ACTION: - PiAction piAction = (PiAction) piTableAction; - Action theAction = encodePiAction(piAction, browser); - tableActionMsgBuilder.setAction(theAction); - break; - case ACTION_PROFILE_GROUP_ID: - PiActionProfileGroupId actionGroupId = (PiActionProfileGroupId) piTableAction; - tableActionMsgBuilder.setActionProfileGroupId(actionGroupId.id()); - break; - case ACTION_PROFILE_MEMBER_ID: - PiActionProfileMemberId actionProfileMemberId = (PiActionProfileMemberId) piTableAction; - tableActionMsgBuilder.setActionProfileMemberId(actionProfileMemberId.id()); - break; - default: - throw new CodecException( - format("Building of table action type %s not implemented", piTableAction.type())); - } - - return tableActionMsgBuilder.build(); - } - - static PiTableAction decodeTableActionMsg(TableAction tableActionMsg, P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - TableAction.TypeCase typeCase = tableActionMsg.getTypeCase(); - switch (typeCase) { - case ACTION: - Action actionMsg = tableActionMsg.getAction(); - return decodeActionMsg(actionMsg, browser); - case ACTION_PROFILE_GROUP_ID: - return PiActionProfileGroupId.of(tableActionMsg.getActionProfileGroupId()); - case ACTION_PROFILE_MEMBER_ID: - return PiActionProfileMemberId.of(tableActionMsg.getActionProfileMemberId()); - default: - throw new CodecException( - format("Decoding of table action type %s not implemented", typeCase.name())); - } - } - - static Action encodePiAction(PiAction piAction, P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException, CodecException { - - int actionId = browser.actions().getByName(piAction.id().toString()).getPreamble().getId(); - - Action.Builder actionMsgBuilder = - Action.newBuilder().setActionId(actionId); - - for (PiActionParam p : piAction.parameters()) { - P4InfoOuterClass.Action.Param paramInfo = browser.actionParams(actionId).getByName(p.id().toString()); - ByteString paramValue = ByteString.copyFrom(p.value().asReadOnlyBuffer()); - assertSize(format("param '%s' of action '%s'", p.id(), piAction.id()), - paramValue, paramInfo.getBitwidth()); - actionMsgBuilder.addParams(Action.Param.newBuilder() - .setParamId(paramInfo.getId()) - .setValue(paramValue) - .build()); - } - - return actionMsgBuilder.build(); - } - - static PiAction decodeActionMsg(Action action, P4InfoBrowser browser) - throws P4InfoBrowser.NotFoundException { - P4InfoBrowser.EntityBrowser paramInfo = - browser.actionParams(action.getActionId()); - String actionName = browser.actions() - .getById(action.getActionId()) - .getPreamble().getName(); - PiActionId id = PiActionId.of(actionName); - List params = Lists.newArrayList(); - - for (Action.Param p : action.getParamsList()) { - String paramName = paramInfo.getById(p.getParamId()).getName(); - ImmutableByteSequence value = ImmutableByteSequence.copyFrom(p.getValue().toByteArray()); - params.add(new PiActionParam(PiActionParamId.of(paramName), value)); - } - return PiAction.builder().withId(id).withParameters(params).build(); - } - - static CounterData encodeCounter(PiCounterCellData piCounterCellData) { - return CounterData.newBuilder().setPacketCount(piCounterCellData.packets()) - .setByteCount(piCounterCellData.bytes()).build(); - } - - static PiCounterCellData decodeCounter(CounterData counterData) { - return new PiCounterCellData(counterData.getPacketCount(), counterData.getByteCount()); - } -} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/P4RuntimeClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/P4RuntimeClientImpl.java new file mode 100644 index 0000000000..353d44e0d4 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/P4RuntimeClientImpl.java @@ -0,0 +1,255 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.client; + +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; +import org.onosproject.grpc.ctl.AbstractGrpcClient; +import org.onosproject.net.DeviceId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiPacketOperation; +import org.onosproject.net.pi.service.PiPipeconfService; +import org.onosproject.p4runtime.api.P4RuntimeClient; +import org.onosproject.p4runtime.api.P4RuntimeClientKey; +import org.onosproject.p4runtime.api.P4RuntimeEvent; +import org.onosproject.p4runtime.ctl.controller.BaseEventSubject; +import org.onosproject.p4runtime.ctl.controller.ChannelEvent; +import org.onosproject.p4runtime.ctl.controller.P4RuntimeControllerImpl; +import p4.v1.P4RuntimeGrpc; +import p4.v1.P4RuntimeOuterClass; + +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; + +/** + * Implementation of P4RuntimeClient. + */ +public final class P4RuntimeClientImpl + extends AbstractGrpcClient implements P4RuntimeClient { + + // TODO: consider making timeouts configurable per-device via netcfg + /** + * Timeout in seconds for short/fast RPCs. + */ + static final int SHORT_TIMEOUT_SECONDS = 10; + /** + * Timeout in seconds for RPCs that involve transfer of potentially large + * amount of data. This shoulld be long enough to allow for network delay + * (e.g. to transfer large pipeline binaries over slow network). + */ + static final int LONG_TIMEOUT_SECONDS = 60; + + private final long p4DeviceId; + private final ManagedChannel channel; + private final P4RuntimeControllerImpl controller; + private final StreamClientImpl streamClient; + private final PipelineConfigClientImpl pipelineConfigClient; + + /** + * Instantiates a new client with the given arguments. + * + * @param clientKey client key + * @param channel gRPC managed channel + * @param controller P$Runtime controller instance + * @param pipeconfService pipeconf service instance + */ + public P4RuntimeClientImpl(P4RuntimeClientKey clientKey, + ManagedChannel channel, + P4RuntimeControllerImpl controller, + PiPipeconfService pipeconfService) { + super(clientKey); + checkNotNull(channel); + checkNotNull(controller); + checkNotNull(pipeconfService); + + this.p4DeviceId = clientKey.p4DeviceId(); + this.channel = channel; + this.controller = controller; + this.streamClient = new StreamClientImpl( + pipeconfService, this, controller); + this.pipelineConfigClient = new PipelineConfigClientImpl(this); + } + + @Override + protected Void doShutdown() { + streamClient.closeSession(); + return super.doShutdown(); + } + + @Override + public CompletableFuture setPipelineConfig( + PiPipeconf pipeconf, ByteBuffer deviceData) { + return pipelineConfigClient.setPipelineConfig(pipeconf, deviceData); + } + + @Override + public CompletableFuture isPipelineConfigSet( + PiPipeconf pipeconf, ByteBuffer deviceData) { + return pipelineConfigClient.isPipelineConfigSet(pipeconf, deviceData); + } + + @Override + public ReadRequest read(PiPipeconf pipeconf) { + return new ReadRequestImpl(this, pipeconf); + } + + @Override + public void openSession() { + streamClient.openSession(); + } + + @Override + public boolean isSessionOpen() { + return streamClient.isSessionOpen(); + } + + @Override + public void closeSession() { + streamClient.closeSession(); + } + + @Override + public void runForMastership() { + streamClient.runForMastership(); + } + + @Override + public boolean isMaster() { + return streamClient.isMaster(); + } + + @Override + public void packetOut(PiPacketOperation packet, PiPipeconf pipeconf) { + streamClient.packetOut(packet, pipeconf); + } + + @Override + public WriteRequest write(PiPipeconf pipeconf) { + return new WriteRequestImpl(this, pipeconf); + } + + /** + * Returns the P4Runtime-internal device ID associated with this client. + * + * @return P4Runtime-internal device ID + */ + long p4DeviceId() { + return this.p4DeviceId; + } + + /** + * Returns the ONOS device ID associated with this client. + * + * @return ONOS device ID + */ + DeviceId deviceId() { + return this.deviceId; + } + + /** + * Returns the election ID last used in a MasterArbitrationUpdate message + * sent by the client to the server. No guarantees are given that this is + * the current election ID associated to the session, nor that the server + * has acknowledged this value as valid. + * + * @return election ID uint128 protobuf message + */ + P4RuntimeOuterClass.Uint128 lastUsedElectionId() { + return streamClient.lastUsedElectionId(); + } + + /** + * Forces execution of an RPC in a cancellable context with the given + * timeout (in seconds). + * + * @param stubConsumer P4Runtime stub consumer + * @param timeout timeout in seconds + */ + void execRpc(Consumer stubConsumer, int timeout) { + if (log.isTraceEnabled()) { + log.trace("Executing RPC with timeout {} seconds (context deadline {})...", + timeout, context().getDeadline()); + } + runInCancellableContext(() -> stubConsumer.accept( + P4RuntimeGrpc.newStub(channel) + .withDeadlineAfter(timeout, TimeUnit.SECONDS))); + } + + /** + * Forces execution of an RPC in a cancellable context with no timeout. + * + * @param stubConsumer P4Runtime stub consumer + */ + void execRpcNoTimeout(Consumer stubConsumer) { + if (log.isTraceEnabled()) { + log.trace("Executing RPC with no timeout (context deadline {})...", + context().getDeadline()); + } + runInCancellableContext(() -> stubConsumer.accept( + P4RuntimeGrpc.newStub(channel))); + } + + /** + * Logs the error and checks it for any condition that might be of interest + * for the controller. + * + * @param throwable throwable + * @param opDescription operation description for logging + */ + void handleRpcError(Throwable throwable, String opDescription) { + if (throwable instanceof StatusRuntimeException) { + final StatusRuntimeException sre = (StatusRuntimeException) throwable; + checkGrpcException(sre); + final String logMsg; + if (sre.getCause() == null) { + logMsg = sre.getMessage(); + } else { + logMsg = format("%s (%s)", sre.getMessage(), sre.getCause().toString()); + } + log.warn("Error while performing {} on {}: {}", + opDescription, deviceId, logMsg); + log.debug("", throwable); + return; + } + log.error(format("Exception while performing %s on %s", + opDescription, deviceId), throwable); + } + + private void checkGrpcException(StatusRuntimeException sre) { + switch (sre.getStatus().getCode()) { + case PERMISSION_DENIED: + // Notify upper layers that this node is not master. + controller.postEvent(new P4RuntimeEvent( + P4RuntimeEvent.Type.PERMISSION_DENIED, + new BaseEventSubject(deviceId))); + break; + case UNAVAILABLE: + // Channel might be closed. + controller.postEvent(new P4RuntimeEvent( + P4RuntimeEvent.Type.CHANNEL_EVENT, + new ChannelEvent(deviceId, ChannelEvent.Type.ERROR))); + break; + default: + break; + } + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/PipelineConfigClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/PipelineConfigClientImpl.java new file mode 100644 index 0000000000..c023f26642 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/PipelineConfigClientImpl.java @@ -0,0 +1,237 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.client; + +import com.google.protobuf.ByteString; +import com.google.protobuf.TextFormat; +import io.grpc.stub.StreamObserver; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.p4runtime.api.P4RuntimePipelineConfigClient; +import org.onosproject.p4runtime.ctl.utils.PipeconfHelper; +import org.slf4j.Logger; +import p4.config.v1.P4InfoOuterClass; +import p4.tmp.P4Config; +import p4.v1.P4RuntimeOuterClass.ForwardingPipelineConfig; +import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest; +import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigResponse; +import p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest; +import p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigResponse; + +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.onosproject.p4runtime.ctl.client.P4RuntimeClientImpl.LONG_TIMEOUT_SECONDS; +import static org.slf4j.LoggerFactory.getLogger; +import static p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest.ResponseType.COOKIE_ONLY; +import static p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest.Action.VERIFY_AND_COMMIT; + +/** + * Implementation of P4RuntimePipelineConfigClient. Handles pipeline + * config-related RPCs. + */ +final class PipelineConfigClientImpl implements P4RuntimePipelineConfigClient { + + private static final Logger log = getLogger(PipelineConfigClientImpl.class); + + private static final SetForwardingPipelineConfigResponse DEFAULT_SET_RESPONSE = + SetForwardingPipelineConfigResponse.getDefaultInstance(); + + private final P4RuntimeClientImpl client; + + PipelineConfigClientImpl(P4RuntimeClientImpl client) { + this.client = client; + } + + @Override + public CompletableFuture setPipelineConfig( + PiPipeconf pipeconf, ByteBuffer deviceData) { + + log.info("Setting pipeline config for {} to {}...", + client.deviceId(), pipeconf.id()); + + checkNotNull(deviceData, "deviceData cannot be null"); + + final ForwardingPipelineConfig pipelineConfigMsg = + buildForwardingPipelineConfigMsg(pipeconf, deviceData); + if (pipelineConfigMsg == null) { + // Error logged in buildForwardingPipelineConfigMsg() + return completedFuture(false); + } + + final SetForwardingPipelineConfigRequest requestMsg = + SetForwardingPipelineConfigRequest + .newBuilder() + .setDeviceId(client.p4DeviceId()) + .setElectionId(client.lastUsedElectionId()) + .setAction(VERIFY_AND_COMMIT) + .setConfig(pipelineConfigMsg) + .build(); + + final CompletableFuture future = new CompletableFuture<>(); + final StreamObserver responseObserver = + new StreamObserver() { + @Override + public void onNext(SetForwardingPipelineConfigResponse value) { + if (!DEFAULT_SET_RESPONSE.equals(value)) { + log.warn("Received invalid SetForwardingPipelineConfigResponse " + + " from {} [{}]", + client.deviceId(), + TextFormat.shortDebugString(value)); + future.complete(false); + } + // All good, pipeline is set. + future.complete(true); + } + @Override + public void onError(Throwable t) { + client.handleRpcError(t, "SET-pipeline-config"); + future.complete(false); + } + @Override + public void onCompleted() { + // Ignore, unary call. + } + }; + + client.execRpc( + s -> s.setForwardingPipelineConfig(requestMsg, responseObserver), + LONG_TIMEOUT_SECONDS); + + return future; + } + + private ForwardingPipelineConfig buildForwardingPipelineConfigMsg( + PiPipeconf pipeconf, ByteBuffer deviceData) { + + final P4InfoOuterClass.P4Info p4Info = PipeconfHelper.getP4Info(pipeconf); + if (p4Info == null) { + // Problem logged by PipeconfHelper. + return null; + } + final ForwardingPipelineConfig.Cookie cookieMsg = + ForwardingPipelineConfig.Cookie + .newBuilder() + .setCookie(pipeconf.fingerprint()) + .build(); + // FIXME: This is specific to PI P4Runtime implementation and should be + // moved to driver. + final P4Config.P4DeviceConfig p4DeviceConfigMsg = P4Config.P4DeviceConfig + .newBuilder() + .setExtras(P4Config.P4DeviceConfig.Extras.getDefaultInstance()) + .setReassign(true) + .setDeviceData(ByteString.copyFrom(deviceData)) + .build(); + return ForwardingPipelineConfig + .newBuilder() + .setP4Info(p4Info) + .setP4DeviceConfig(p4DeviceConfigMsg.toByteString()) + .setCookie(cookieMsg) + .build(); + } + + + @Override + public CompletableFuture isPipelineConfigSet( + PiPipeconf pipeconf, ByteBuffer expectedDeviceData) { + return getPipelineCookieFromServer() + .thenApply(cfgFromDevice -> comparePipelineConfig( + pipeconf, expectedDeviceData, cfgFromDevice)); + } + + private boolean comparePipelineConfig( + PiPipeconf pipeconf, ByteBuffer expectedDeviceData, + ForwardingPipelineConfig cfgFromDevice) { + if (cfgFromDevice == null) { + return false; + } + + final ForwardingPipelineConfig expectedCfg = buildForwardingPipelineConfigMsg( + pipeconf, expectedDeviceData); + if (expectedCfg == null) { + return false; + } + + if (cfgFromDevice.hasCookie()) { + return cfgFromDevice.getCookie().getCookie() == pipeconf.fingerprint(); + } + + // No cookie. + log.warn("{} returned GetForwardingPipelineConfigResponse " + + "with 'cookie' field unset. " + + "Will try by comparing 'device_data' and 'p4_info'...", + client.deviceId()); + + if (cfgFromDevice.getP4DeviceConfig().isEmpty() + && !expectedCfg.getP4DeviceConfig().isEmpty()) { + // Don't bother with a warn or error since we don't really allow + // updating the P4 blob to a different one without changing the + // P4Info. I.e, comparing just the P4Info should be enough for us. + log.debug("{} returned GetForwardingPipelineConfigResponse " + + "with empty 'p4_device_config' field, " + + "equality will be based only on P4Info", + client.deviceId()); + return cfgFromDevice.getP4Info().equals(expectedCfg.getP4Info()); + } + + return cfgFromDevice.getP4DeviceConfig() + .equals(expectedCfg.getP4DeviceConfig()) + && cfgFromDevice.getP4Info() + .equals(expectedCfg.getP4Info()); + } + + private CompletableFuture getPipelineCookieFromServer() { + final GetForwardingPipelineConfigRequest request = + GetForwardingPipelineConfigRequest + .newBuilder() + .setDeviceId(client.p4DeviceId()) + .setResponseType(COOKIE_ONLY) + .build(); + final CompletableFuture future = new CompletableFuture<>(); + final StreamObserver responseObserver = + new StreamObserver() { + @Override + public void onNext(GetForwardingPipelineConfigResponse value) { + if (value.hasConfig()) { + future.complete(value.getConfig()); + } else { + log.warn("{} returned {} with 'config' field unset", + client.deviceId(), value.getClass().getSimpleName()); + } + future.complete(null); + } + + @Override + public void onError(Throwable t) { + client.handleRpcError(t, "GET-pipeline-config"); + future.complete(null); + } + + @Override + public void onCompleted() { + // Ignore, unary call. + } + }; + // Use long timeout as the device might return the full P4 blob + // (e.g. server does not support cookie), over a slow network. + client.execRpc( + s -> s.getForwardingPipelineConfig(request, responseObserver), + LONG_TIMEOUT_SECONDS); + return future; + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/ReadRequestImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/ReadRequestImpl.java new file mode 100644 index 0000000000..c85c7e8efc --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/ReadRequestImpl.java @@ -0,0 +1,386 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.client; + +import com.google.common.util.concurrent.Futures; +import io.grpc.stub.StreamObserver; +import org.onosproject.net.pi.model.PiActionProfileId; +import org.onosproject.net.pi.model.PiCounterId; +import org.onosproject.net.pi.model.PiMeterId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.model.PiTableId; +import org.onosproject.net.pi.runtime.PiHandle; +import org.onosproject.p4runtime.api.P4RuntimeReadClient; +import org.onosproject.p4runtime.ctl.codec.CodecException; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import org.onosproject.p4runtime.ctl.utils.PipeconfHelper; +import org.slf4j.Logger; +import p4.v1.P4RuntimeOuterClass; + +import java.util.concurrent.CompletableFuture; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.onosproject.p4runtime.ctl.client.P4RuntimeClientImpl.SHORT_TIMEOUT_SECONDS; +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Handles the creation of P4Runtime ReadRequest and execution of the Read RPC + * on the server. + */ +public final class ReadRequestImpl implements P4RuntimeReadClient.ReadRequest { + + private static final Logger log = getLogger(ReadRequestImpl.class); + + private final P4RuntimeClientImpl client; + private final PiPipeconf pipeconf; + private final P4RuntimeOuterClass.ReadRequest.Builder requestMsg; + + ReadRequestImpl(P4RuntimeClientImpl client, PiPipeconf pipeconf) { + this.client = client; + this.pipeconf = pipeconf; + this.requestMsg = P4RuntimeOuterClass.ReadRequest.newBuilder() + .setDeviceId(client.p4DeviceId()); + } + + @Override + public P4RuntimeReadClient.ReadRequest handles(Iterable handles) { + checkNotNull(handles); + handles.forEach(this::handle); + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest tableEntries(Iterable tableIds) { + checkNotNull(tableIds); + tableIds.forEach(this::tableEntries); + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest defaultTableEntry(Iterable tableIds) { + checkNotNull(tableIds); + tableIds.forEach(this::defaultTableEntry); + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest actionProfileGroups(Iterable actionProfileIds) { + checkNotNull(actionProfileIds); + actionProfileIds.forEach(this::actionProfileGroups); + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest actionProfileMembers(Iterable actionProfileIds) { + checkNotNull(actionProfileIds); + actionProfileIds.forEach(this::actionProfileMembers); + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest counterCells(Iterable counterIds) { + checkNotNull(counterIds); + counterIds.forEach(this::counterCells); + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest directCounterCells(Iterable tableIds) { + checkNotNull(tableIds); + tableIds.forEach(this::directCounterCells); + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest meterCells(Iterable meterIds) { + checkNotNull(meterIds); + meterIds.forEach(this::meterCells); + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest directMeterCells(Iterable tableIds) { + checkNotNull(tableIds); + tableIds.forEach(this::directMeterCells); + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest handle(PiHandle handle) { + checkNotNull(handle); + try { + requestMsg.addEntities(CODECS.handle().encode(handle, null, pipeconf)); + } catch (CodecException e) { + log.warn("Unable to read {} from {}: {} [{}]", + handle.entityType(), client.deviceId(), e.getMessage(), handle); + } + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest tableEntries(PiTableId tableId) { + try { + doTableEntry(tableId, false); + } catch (InternalRequestException e) { + log.warn("Unable to read entries for table '{}' from {}: {}", + tableId, client.deviceId(), e.getMessage()); + } + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest defaultTableEntry(PiTableId tableId) { + try { + doTableEntry(tableId, true); + } catch (InternalRequestException e) { + log.warn("Unable to read default entry for table '{}' from {}: {}", + tableId, client.deviceId(), e.getMessage()); + } + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest actionProfileGroups(PiActionProfileId actionProfileId) { + try { + requestMsg.addEntities( + P4RuntimeOuterClass.Entity.newBuilder() + .setActionProfileGroup( + P4RuntimeOuterClass.ActionProfileGroup.newBuilder() + .setActionProfileId( + p4ActionProfileId(actionProfileId)) + .build()) + .build()); + } catch (InternalRequestException e) { + log.warn("Unable to read groups for action profile '{}' from {}: {}", + actionProfileId, client.deviceId(), e.getMessage()); + } + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest actionProfileMembers(PiActionProfileId actionProfileId) { + try { + requestMsg.addEntities( + P4RuntimeOuterClass.Entity.newBuilder() + .setActionProfileMember( + P4RuntimeOuterClass.ActionProfileMember.newBuilder() + .setActionProfileId( + p4ActionProfileId(actionProfileId)) + .build()) + .build()); + } catch (InternalRequestException e) { + log.warn("Unable to read members for action profile '{}' from {}: {}", + actionProfileId, client.deviceId(), e.getMessage()); + } + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest counterCells(PiCounterId counterId) { + try { + requestMsg.addEntities( + P4RuntimeOuterClass.Entity.newBuilder() + .setCounterEntry( + P4RuntimeOuterClass.CounterEntry.newBuilder() + .setCounterId(p4CounterId(counterId)) + .build()) + .build()); + } catch (InternalRequestException e) { + log.warn("Unable to read cells for counter '{}' from {}: {}", + counterId, client.deviceId(), e.getMessage()); + } + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest meterCells(PiMeterId meterId) { + try { + requestMsg.addEntities( + P4RuntimeOuterClass.Entity.newBuilder() + .setMeterEntry( + P4RuntimeOuterClass.MeterEntry.newBuilder() + .setMeterId(p4MeterId(meterId)) + .build()) + .build()); + } catch (InternalRequestException e) { + log.warn("Unable to read cells for meter '{}' from {}: {}", + meterId, client.deviceId(), e.getMessage()); + } + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest directCounterCells(PiTableId tableId) { + try { + requestMsg.addEntities( + P4RuntimeOuterClass.Entity.newBuilder() + .setDirectCounterEntry( + P4RuntimeOuterClass.DirectCounterEntry.newBuilder() + .setTableEntry( + P4RuntimeOuterClass.TableEntry + .newBuilder() + .setTableId(p4TableId(tableId)) + .build()) + .build()) + .build()); + } catch (InternalRequestException e) { + log.warn("Unable to read direct counter cells for table '{}' from {}: {}", + tableId, client.deviceId(), e.getMessage()); + } + return this; + } + + @Override + public P4RuntimeReadClient.ReadRequest directMeterCells(PiTableId tableId) { + try { + requestMsg.addEntities( + P4RuntimeOuterClass.Entity.newBuilder() + .setDirectMeterEntry( + P4RuntimeOuterClass.DirectMeterEntry.newBuilder() + .setTableEntry( + P4RuntimeOuterClass.TableEntry + .newBuilder() + .setTableId(p4TableId(tableId)) + .build()) + .build()) + .build()); + } catch (InternalRequestException e) { + log.warn("Unable to read direct meter cells for table '{}' from {}: {}", + tableId, client.deviceId(), e.getMessage()); + } + return this; + } + + private void doTableEntry(PiTableId piTableId, boolean defaultEntries) + throws InternalRequestException { + checkNotNull(piTableId); + final P4RuntimeOuterClass.Entity entityMsg = P4RuntimeOuterClass.Entity + .newBuilder() + .setTableEntry( + P4RuntimeOuterClass.TableEntry.newBuilder() + .setTableId(p4TableId(piTableId)) + .setIsDefaultAction(defaultEntries) + .setCounterData(P4RuntimeOuterClass.CounterData + .getDefaultInstance()) + .build()) + .build(); + requestMsg.addEntities(entityMsg); + } + + @Override + public CompletableFuture submit() { + final P4RuntimeOuterClass.ReadRequest readRequest = requestMsg.build(); + log.debug("Sending read request to {} for {} entities...", + client.deviceId(), readRequest.getEntitiesCount()); + if (readRequest.getEntitiesCount() == 0) { + // No need to ask the server. + return completedFuture(ReadResponseImpl.EMPTY); + } + final CompletableFuture future = + new CompletableFuture<>(); + // Instantiate response builder and let stream observer populate it. + final ReadResponseImpl.Builder responseBuilder = + ReadResponseImpl.builder(client.deviceId(), pipeconf); + final StreamObserver observer = + new StreamObserver() { + @Override + public void onNext(P4RuntimeOuterClass.ReadResponse value) { + log.debug("Received read response from {} with {} entities...", + client.deviceId(), value.getEntitiesCount()); + value.getEntitiesList().forEach(responseBuilder::addEntity); + } + @Override + public void onError(Throwable t) { + client.handleRpcError(t, "READ"); + // TODO: implement parsing of trailer errors + future.complete(responseBuilder.fail(t)); + } + @Override + public void onCompleted() { + future.complete(responseBuilder.build()); + } + }; + client.execRpc(s -> s.read(readRequest, observer), SHORT_TIMEOUT_SECONDS); + return future; + } + + @Override + public P4RuntimeReadClient.ReadResponse submitSync() { + return Futures.getUnchecked(submit()); + } + + private int p4TableId(PiTableId piTableId) throws InternalRequestException { + try { + return getBrowser().tables().getByName(piTableId.id()) + .getPreamble().getId(); + } catch (P4InfoBrowser.NotFoundException e) { + throw new InternalRequestException(e.getMessage()); + } + } + + private int p4ActionProfileId(PiActionProfileId piActionProfileId) + throws InternalRequestException { + try { + return getBrowser().actionProfiles().getByName(piActionProfileId.id()) + .getPreamble().getId(); + } catch (P4InfoBrowser.NotFoundException e) { + throw new InternalRequestException(e.getMessage()); + } + } + + private int p4CounterId(PiCounterId counterId) + throws InternalRequestException { + try { + return getBrowser().counters().getByName(counterId.id()) + .getPreamble().getId(); + } catch (P4InfoBrowser.NotFoundException e) { + throw new InternalRequestException(e.getMessage()); + } + } + + private int p4MeterId(PiMeterId meterId) + throws InternalRequestException { + try { + return getBrowser().meters().getByName(meterId.id()) + .getPreamble().getId(); + } catch (P4InfoBrowser.NotFoundException e) { + throw new InternalRequestException(e.getMessage()); + } + } + + private P4InfoBrowser getBrowser() throws InternalRequestException { + final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); + if (browser == null) { + throw new InternalRequestException( + "Unable to get a P4Info browser for pipeconf " + pipeconf.id()); + } + return browser; + } + + /** + * Internal exception to signal that something went wrong when populating + * the request. + */ + private final class InternalRequestException extends Exception { + + private InternalRequestException(String message) { + super(message); + } + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/ReadResponseImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/ReadResponseImpl.java new file mode 100644 index 0000000000..5e577977fe --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/ReadResponseImpl.java @@ -0,0 +1,147 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.client; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.protobuf.TextFormat; +import org.onosproject.net.DeviceId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiEntity; +import org.onosproject.p4runtime.api.P4RuntimeReadClient; +import org.onosproject.p4runtime.ctl.codec.CodecException; +import org.slf4j.Logger; +import p4.v1.P4RuntimeOuterClass; + +import java.util.Collection; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Handles creation of ReadResponse by parsing Read RPC server responses. + */ +public final class ReadResponseImpl implements P4RuntimeReadClient.ReadResponse { + + private static final Logger log = getLogger(ReadResponseImpl.class); + + public static final ReadResponseImpl EMPTY = new ReadResponseImpl( + true, ImmutableList.of(), ImmutableListMultimap.of(), null, null); + + private final boolean success; + private final ImmutableList entities; + private final ImmutableListMultimap, PiEntity> typeToEntities; + private final String explanation; + private final Throwable throwable; + + private ReadResponseImpl( + boolean success, ImmutableList entities, + ImmutableListMultimap, PiEntity> typeToEntities, + String explanation, Throwable throwable) { + this.success = success; + this.entities = entities; + this.typeToEntities = typeToEntities; + this.explanation = explanation; + this.throwable = throwable; + } + + @Override + public boolean isSuccess() { + return success; + } + + @Override + public Collection all() { + return entities; + } + + @Override + @SuppressWarnings("unchecked") + public Collection all(Class clazz) { + return (ImmutableList) typeToEntities.get(clazz); + } + + @Override + public String explanation() { + return explanation; + } + + @Override + public Throwable throwable() { + return throwable; + } + + static Builder builder(DeviceId deviceId, PiPipeconf pipeconf) { + return new Builder(deviceId, pipeconf); + } + + /** + * Builder of P4RuntimeReadResponseImpl. + */ + static final class Builder { + + private final DeviceId deviceId; + private final PiPipeconf pipeconf; + private final List entities = Lists.newArrayList(); + private final ListMultimap, PiEntity> + typeToEntities = ArrayListMultimap.create(); + + private boolean success = true; + private String explanation; + private Throwable throwable; + + private Builder(DeviceId deviceId, PiPipeconf pipeconf) { + this.deviceId = deviceId; + this.pipeconf = pipeconf; + } + + void addEntity(P4RuntimeOuterClass.Entity entityMsg) { + try { + final PiEntity piEntity = CODECS.entity().decode(entityMsg, null, pipeconf); + entities.add(piEntity); + typeToEntities.put(piEntity.getClass(), piEntity); + } catch (CodecException e) { + log.warn("Unable to decode {} message from {}: {} [{}]", + entityMsg.getEntityCase().name(), deviceId, + e.getMessage(), TextFormat.shortDebugString(entityMsg)); + } + } + + ReadResponseImpl fail(Throwable throwable) { + checkNotNull(throwable); + this.success = false; + this.explanation = throwable.getMessage(); + this.throwable = throwable; + return build(); + } + + ReadResponseImpl build() { + if (success && entities.isEmpty()) { + return EMPTY; + } + return new ReadResponseImpl( + success, ImmutableList.copyOf(entities), + ImmutableListMultimap.copyOf(typeToEntities), + explanation, throwable); + } + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/StreamClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/StreamClientImpl.java new file mode 100644 index 0000000000..7afd97ba08 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/StreamClientImpl.java @@ -0,0 +1,404 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.client; + +import com.google.protobuf.TextFormat; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.ClientCallStreamObserver; +import io.grpc.stub.StreamObserver; +import org.onosproject.net.DeviceId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiPacketOperation; +import org.onosproject.net.pi.service.PiPipeconfService; +import org.onosproject.p4runtime.api.P4RuntimeEvent; +import org.onosproject.p4runtime.api.P4RuntimeStreamClient; +import org.onosproject.p4runtime.ctl.codec.CodecException; +import org.onosproject.p4runtime.ctl.controller.ArbitrationUpdateEvent; +import org.onosproject.p4runtime.ctl.controller.ChannelEvent; +import org.onosproject.p4runtime.ctl.controller.P4RuntimeControllerImpl; +import org.onosproject.p4runtime.ctl.controller.PacketInEvent; +import org.slf4j.Logger; +import p4.v1.P4RuntimeOuterClass; +import p4.v1.P4RuntimeOuterClass.StreamMessageRequest; +import p4.v1.P4RuntimeOuterClass.StreamMessageResponse; + +import java.math.BigInteger; +import java.net.ConnectException; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; + +import static java.lang.String.format; +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Implementation of P4RuntimeStreamClient. Handles P4Runtime StreamChannel RPC + * operations, such as arbitration update and packet-in/out. + */ +public final class StreamClientImpl implements P4RuntimeStreamClient { + + private static final Logger log = getLogger(StreamClientImpl.class); + + private static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000); + + private final P4RuntimeClientImpl client; + private final DeviceId deviceId; + private final long p4DeviceId; + private final PiPipeconfService pipeconfService; + private final P4RuntimeControllerImpl controller; + private final StreamChannelManager streamChannelManager = new StreamChannelManager(); + + private P4RuntimeOuterClass.Uint128 lastUsedElectionId = P4RuntimeOuterClass.Uint128 + .newBuilder().setLow(1).build(); + + private final AtomicBoolean isClientMaster = new AtomicBoolean(false); + + StreamClientImpl( + PiPipeconfService pipeconfService, + P4RuntimeClientImpl client, + P4RuntimeControllerImpl controller) { + this.client = client; + this.deviceId = client.deviceId(); + this.p4DeviceId = client.p4DeviceId(); + this.pipeconfService = pipeconfService; + this.controller = controller; + } + + @Override + public void openSession() { + if (isSessionOpen()) { + log.debug("Dropping request to open session for {}, session is already open", + deviceId); + return; + } + log.debug("Opening session for {}...", deviceId); + sendMasterArbitrationUpdate(controller.newMasterElectionId(deviceId)); + + } + + @Override + public boolean isSessionOpen() { + return streamChannelManager.isOpen(); + } + + @Override + public void closeSession() { + streamChannelManager.complete(); + } + + @Override + public void runForMastership() { + if (!isSessionOpen()) { + log.debug("Dropping mastership request for {}, session is closed", + deviceId); + return; + } + // Becoming master is a race. Here we increase our chances of win, i.e. + // using the highest election ID, against other ONOS nodes in the + // cluster that are calling openSession() (which is used to start the + // stream RPC session, not to become master). + log.debug("Running for mastership on {}...", deviceId); + final BigInteger masterId = controller.newMasterElectionId(deviceId) + .add(ONE_THOUSAND); + sendMasterArbitrationUpdate(masterId); + } + + @Override + public boolean isMaster() { + return streamChannelManager.isOpen() && isClientMaster.get(); + } + + @Override + public void packetOut(PiPacketOperation packet, PiPipeconf pipeconf) { + if (!isSessionOpen()) { + log.debug("Dropping packet-out request for {}, session is closed", + deviceId); + return; + } + if (log.isTraceEnabled()) { + log.trace("Sending packet-out to {}: {}", deviceId, packet); + } + try { + // Encode the PiPacketOperation into a PacketOut + final P4RuntimeOuterClass.PacketOut packetOut = + CODECS.packetOut().encode(packet, null, pipeconf); + // Build the request + final StreamMessageRequest packetOutRequest = StreamMessageRequest + .newBuilder().setPacket(packetOut).build(); + // Send. + streamChannelManager.sendIfOpen(packetOutRequest); + } catch (CodecException e) { + log.error("Unable to send packet-out: {}", e.getMessage()); + } + } + + private void sendMasterArbitrationUpdate(BigInteger electionId) { + log.debug("Sending arbitration update to {}... electionId={}", + deviceId, electionId); + final P4RuntimeOuterClass.Uint128 idMsg = bigIntegerToUint128(electionId); + streamChannelManager.send( + StreamMessageRequest.newBuilder() + .setArbitration( + P4RuntimeOuterClass.MasterArbitrationUpdate + .newBuilder() + .setDeviceId(p4DeviceId) + .setElectionId(idMsg) + .build()) + .build()); + lastUsedElectionId = idMsg; + } + + private P4RuntimeOuterClass.Uint128 bigIntegerToUint128(BigInteger value) { + final byte[] arr = value.toByteArray(); + final ByteBuffer bb = ByteBuffer.allocate(Long.BYTES * 2) + .put(new byte[Long.BYTES * 2 - arr.length]) + .put(arr); + bb.rewind(); + return P4RuntimeOuterClass.Uint128.newBuilder() + .setHigh(bb.getLong()) + .setLow(bb.getLong()) + .build(); + } + + private BigInteger uint128ToBigInteger(P4RuntimeOuterClass.Uint128 value) { + return new BigInteger( + ByteBuffer.allocate(Long.BYTES * 2) + .putLong(value.getHigh()) + .putLong(value.getLow()) + .array()); + } + + private void handlePacketIn(P4RuntimeOuterClass.PacketIn packetInMsg) { + if (log.isTraceEnabled()) { + log.trace("Received packet-in from {}: {}", deviceId, packetInMsg); + } + if (!pipeconfService.getPipeconf(deviceId).isPresent()) { + log.warn("Unable to handle packet-in from {}, missing pipeconf: {}", + deviceId, TextFormat.shortDebugString(packetInMsg)); + return; + } + // Decode packet message and post event. + // TODO: consider implementing a cache to speed up + // encoding/deconding of packet-in/out (e.g. LLDP, ARP) + final PiPipeconf pipeconf = pipeconfService.getPipeconf(deviceId).get(); + final PiPacketOperation pktOperation; + try { + pktOperation = CODECS.packetIn().decode( + packetInMsg, null, pipeconf); + } catch (CodecException e) { + log.warn("Unable to process packet-int: {}", e.getMessage()); + return; + } + controller.postEvent(new P4RuntimeEvent( + P4RuntimeEvent.Type.PACKET_IN, + new PacketInEvent(deviceId, pktOperation))); + } + + private void handleArbitrationUpdate(P4RuntimeOuterClass.MasterArbitrationUpdate msg) { + // From the spec... + // - Election_id: The stream RPC with the highest election_id is the + // master. Switch populates with the highest election ID it + // has received from all connected controllers. + // - Status: Switch populates this with OK for the client that is the + // master, and with an error status for all other connected clients (at + // every mastership change). + if (!msg.hasElectionId() || !msg.hasStatus()) { + return; + } + final boolean isMaster = msg.getStatus().getCode() == Status.OK.getCode().value(); + log.debug("Received arbitration update from {}: isMaster={}, electionId={}", + deviceId, isMaster, uint128ToBigInteger(msg.getElectionId())); + controller.postEvent(new P4RuntimeEvent( + P4RuntimeEvent.Type.ARBITRATION_RESPONSE, + new ArbitrationUpdateEvent(deviceId, isMaster))); + isClientMaster.set(isMaster); + } + + /** + * Returns the election ID last used in a MasterArbitrationUpdate message + * sent by the client to the server. + * + * @return election ID uint128 protobuf message + */ + P4RuntimeOuterClass.Uint128 lastUsedElectionId() { + return lastUsedElectionId; + } + + /** + * A manager for the P4Runtime stream channel that opportunistically creates + * new stream RCP stubs (e.g. when one fails because of errors) and posts + * channel events via the P4Runtime controller. + */ + private final class StreamChannelManager { + + private final AtomicBoolean open = new AtomicBoolean(false); + private final StreamObserver responseObserver = + new InternalStreamResponseObserver(this); + private ClientCallStreamObserver requestObserver; + + void send(StreamMessageRequest value) { + synchronized (this) { + initIfRequired(); + doSend(value); + } + } + + void sendIfOpen(StreamMessageRequest value) { + // We do not lock here, but we ignore NPEs due to stream RPC not + // being active (null requestObserver). Good for frequent + // packet-outs. + try { + doSend(value); + } catch (NullPointerException e) { + if (requestObserver != null) { + // Must be something else. + throw e; + } + } + } + + private void doSend(StreamMessageRequest value) { + try { + requestObserver.onNext(value); + } catch (Throwable ex) { + if (ex instanceof StatusRuntimeException) { + log.warn("Unable to send {} to {}: {}", + value.getUpdateCase().toString(), deviceId, ex.getMessage()); + } else { + log.error("Exception while sending {} to {}: {}", + value.getUpdateCase().toString(), deviceId, ex); + } + complete(); + } + } + + private void initIfRequired() { + if (requestObserver == null) { + log.debug("Creating new stream channel for {}...", deviceId); + open.set(false); + client.execRpcNoTimeout( + s -> requestObserver = + (ClientCallStreamObserver) + s.streamChannel(responseObserver) + ); + } + } + + void complete() { + synchronized (this) { + signalClosed(); + if (requestObserver != null) { + requestObserver.onCompleted(); + requestObserver.cancel("Completed", null); + requestObserver = null; + } + } + } + + void signalOpen() { + synchronized (this) { + final boolean wasOpen = open.getAndSet(true); + if (!wasOpen) { + controller.postEvent(new P4RuntimeEvent( + P4RuntimeEvent.Type.CHANNEL_EVENT, + new ChannelEvent(deviceId, ChannelEvent.Type.OPEN))); + } + } + } + + void signalClosed() { + synchronized (this) { + final boolean wasOpen = open.getAndSet(false); + if (wasOpen) { + controller.postEvent(new P4RuntimeEvent( + P4RuntimeEvent.Type.CHANNEL_EVENT, + new ChannelEvent(deviceId, ChannelEvent.Type.CLOSED))); + } + } + } + + boolean isOpen() { + return open.get(); + } + } + + /** + * Handles messages received from the device on the stream channel. + */ + private final class InternalStreamResponseObserver + implements StreamObserver { + + private final StreamChannelManager streamChannelManager; + + private InternalStreamResponseObserver( + StreamChannelManager streamChannelManager) { + this.streamChannelManager = streamChannelManager; + } + + @Override + public void onNext(StreamMessageResponse message) { + streamChannelManager.signalOpen(); + try { + if (log.isTraceEnabled()) { + log.trace( + "Received {} from {}: {}", + message.getUpdateCase(), deviceId, + TextFormat.shortDebugString(message)); + } + switch (message.getUpdateCase()) { + case PACKET: + handlePacketIn(message.getPacket()); + return; + case ARBITRATION: + handleArbitrationUpdate(message.getArbitration()); + return; + default: + log.warn("Unrecognized StreamMessageResponse from {}: {}", + deviceId, message.getUpdateCase()); + } + } catch (Throwable ex) { + log.error("Exception while processing stream message from {}", + deviceId, ex); + } + } + + @Override + public void onError(Throwable throwable) { + if (throwable instanceof StatusRuntimeException) { + final StatusRuntimeException sre = (StatusRuntimeException) throwable; + if (sre.getStatus().getCause() instanceof ConnectException) { + log.warn("{} is unreachable ({})", + deviceId, sre.getCause().getMessage()); + } else { + log.warn("Error on stream channel for {}: {}", + deviceId, throwable.getMessage()); + } + } else { + log.error(format("Exception on stream channel for %s", + deviceId), throwable); + } + streamChannelManager.complete(); + } + + @Override + public void onCompleted() { + log.warn("Stream channel for {} has completed", deviceId); + streamChannelManager.complete(); + } + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/WriteRequestImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/WriteRequestImpl.java new file mode 100644 index 0000000000..5b5d087d45 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/WriteRequestImpl.java @@ -0,0 +1,219 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.client; + +import com.google.common.util.concurrent.Futures; +import com.google.protobuf.TextFormat; +import io.grpc.stub.StreamObserver; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiEntity; +import org.onosproject.net.pi.runtime.PiHandle; +import org.onosproject.p4runtime.api.P4RuntimeWriteClient; +import org.onosproject.p4runtime.ctl.codec.CodecException; +import org.slf4j.Logger; +import p4.v1.P4RuntimeOuterClass; + +import java.util.concurrent.CompletableFuture; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.onosproject.p4runtime.ctl.client.P4RuntimeClientImpl.SHORT_TIMEOUT_SECONDS; +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Handles the creation of P4Runtime WriteRequest and execution of the Write RPC + * on the server. + */ +final class WriteRequestImpl implements P4RuntimeWriteClient.WriteRequest { + + private static final Logger log = getLogger(WriteRequestImpl.class); + + private static final P4RuntimeOuterClass.WriteResponse P4RT_DEFAULT_WRITE_RESPONSE_MSG = + P4RuntimeOuterClass.WriteResponse.getDefaultInstance(); + + private final P4RuntimeClientImpl client; + private final PiPipeconf pipeconf; + // The P4Runtime WriteRequest protobuf message we need to populate. + private final P4RuntimeOuterClass.WriteRequest.Builder requestMsg; + // WriteResponse instance builder. We populate entity responses as we add new + // entities to this request. The status of each entity response will be + // set once we receive a response from the device. + private final WriteResponseImpl.Builder responseBuilder; + + WriteRequestImpl(P4RuntimeClientImpl client, PiPipeconf pipeconf) { + this.client = checkNotNull(client); + this.pipeconf = checkNotNull(pipeconf); + this.requestMsg = P4RuntimeOuterClass.WriteRequest.newBuilder() + .setDeviceId(client.p4DeviceId()); + this.responseBuilder = WriteResponseImpl.builder(client.deviceId()); + } + + @Override + public P4RuntimeWriteClient.WriteRequest withAtomicity( + P4RuntimeWriteClient.Atomicity atomicity) { + checkNotNull(atomicity); + switch (atomicity) { + case CONTINUE_ON_ERROR: + requestMsg.setAtomicity( + P4RuntimeOuterClass.WriteRequest.Atomicity.CONTINUE_ON_ERROR); + break; + case ROLLBACK_ON_ERROR: + case DATAPLANE_ATOMIC: + // Supporting this while allowing codec exceptions to be + // reported as write responses can be tricky. Assuming write on + // device succeed but we have a codec exception and + // atomicity is rollback on error. + default: + throw new UnsupportedOperationException(format( + "Atomicity mode %s not supported", atomicity)); + } + return this; + } + + @Override + public P4RuntimeWriteClient.WriteRequest insert(PiEntity entity) { + return entity(entity, P4RuntimeWriteClient.UpdateType.INSERT); + } + + @Override + public P4RuntimeWriteClient.WriteRequest insert( + Iterable entities) { + return entities(entities, P4RuntimeWriteClient.UpdateType.INSERT); + } + + @Override + public P4RuntimeWriteClient.WriteRequest modify(PiEntity entity) { + return entity(entity, P4RuntimeWriteClient.UpdateType.MODIFY); + } + + @Override + public P4RuntimeWriteClient.WriteRequest modify( + Iterable entities) { + return entities(entities, P4RuntimeWriteClient.UpdateType.MODIFY); + } + + @Override + public P4RuntimeWriteClient.WriteRequest delete( + Iterable handles) { + checkNotNull(handles); + handles.forEach(this::delete); + return this; + } + + @Override + public P4RuntimeWriteClient.WriteRequest entities( + Iterable entities, + P4RuntimeWriteClient.UpdateType updateType) { + checkNotNull(entities); + entities.forEach(e -> this.entity(e, updateType)); + return this; + } + + @Override + public P4RuntimeWriteClient.WriteRequest entity( + PiEntity entity, P4RuntimeWriteClient.UpdateType updateType) { + checkNotNull(entity); + checkNotNull(updateType); + appendToRequestMsg(updateType, entity, entity.handle(client.deviceId())); + return this; + } + + @Override + public P4RuntimeWriteClient.WriteRequest delete(PiHandle handle) { + checkNotNull(handle); + appendToRequestMsg(P4RuntimeWriteClient.UpdateType.DELETE, null, handle); + return this; + } + + @Override + public P4RuntimeWriteClient.WriteResponse submitSync() { + return Futures.getUnchecked(submit()); + } + + @Override + public CompletableFuture submit() { + final P4RuntimeOuterClass.WriteRequest writeRequest = requestMsg + .setElectionId(client.lastUsedElectionId()) + .build(); + log.debug("Sending write request to {} with {} updates...", + client.deviceId(), writeRequest.getUpdatesCount()); + if (writeRequest.getUpdatesCount() == 0) { + // No need to ask the server. + return completedFuture(WriteResponseImpl.EMPTY); + } + final CompletableFuture future = + new CompletableFuture<>(); + final StreamObserver observer = + new StreamObserver() { + @Override + public void onNext(P4RuntimeOuterClass.WriteResponse value) { + if (!P4RT_DEFAULT_WRITE_RESPONSE_MSG.equals(value)) { + log.warn("Received invalid WriteResponse message from {}: {}", + client.deviceId(), TextFormat.shortDebugString(value)); + // Leave all entity responses in pending state. + future.complete(responseBuilder.buildAsIs()); + } else { + log.debug("Received write response from {}...", + client.deviceId()); + // All good, all entities written successfully. + future.complete(responseBuilder.setSuccessAllAndBuild()); + } + } + @Override + public void onError(Throwable t) { + client.handleRpcError(t, "WRITE"); + future.complete(responseBuilder.setErrorsAndBuild(t)); + } + @Override + public void onCompleted() { + // Nothing to do, unary call. + } + }; + client.execRpc(s -> s.write(writeRequest, observer), SHORT_TIMEOUT_SECONDS); + return future; + } + + private void appendToRequestMsg(P4RuntimeWriteClient.UpdateType updateType, + PiEntity piEntity, PiHandle handle) { + final P4RuntimeOuterClass.Update.Type p4UpdateType; + final P4RuntimeOuterClass.Entity entityMsg; + try { + if (updateType.equals(P4RuntimeWriteClient.UpdateType.DELETE)) { + p4UpdateType = P4RuntimeOuterClass.Update.Type.DELETE; + entityMsg = CODECS.handle().encode(handle, null, pipeconf); + } else { + p4UpdateType = updateType == P4RuntimeWriteClient.UpdateType.INSERT + ? P4RuntimeOuterClass.Update.Type.INSERT + : P4RuntimeOuterClass.Update.Type.MODIFY; + entityMsg = CODECS.entity().encode(piEntity, null, pipeconf); + } + final P4RuntimeOuterClass.Update updateMsg = P4RuntimeOuterClass.Update + .newBuilder() + .setEntity(entityMsg) + .setType(p4UpdateType) + .build(); + requestMsg.addUpdates(updateMsg); + responseBuilder.addPendingResponse(handle, piEntity, updateType); + } catch (CodecException e) { + responseBuilder.addFailedResponse( + handle, piEntity, updateType, e.getMessage(), + P4RuntimeWriteClient.WriteResponseStatus.CODEC_ERROR); + } + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/WriteResponseImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/WriteResponseImpl.java new file mode 100644 index 0000000000..404ab80523 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/WriteResponseImpl.java @@ -0,0 +1,394 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.client; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.TextFormat; +import io.grpc.Metadata; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.protobuf.lite.ProtoLiteUtils; +import org.onosproject.net.DeviceId; +import org.onosproject.net.pi.runtime.PiEntity; +import org.onosproject.net.pi.runtime.PiEntityType; +import org.onosproject.net.pi.runtime.PiHandle; +import org.onosproject.p4runtime.api.P4RuntimeWriteClient; +import org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType; +import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteEntityResponse; +import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteResponseStatus; +import org.slf4j.Logger; +import p4.v1.P4RuntimeOuterClass; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; +import static java.util.stream.Collectors.toList; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Handles the creation of WriteResponse and parsing of P4Runtime errors + * received from server, as well as logging of RPC errors. + */ +final class WriteResponseImpl implements P4RuntimeWriteClient.WriteResponse { + + private static final Metadata.Key STATUS_DETAILS_KEY = + Metadata.Key.of( + "grpc-status-details-bin", + ProtoLiteUtils.metadataMarshaller( + com.google.rpc.Status.getDefaultInstance())); + + static final WriteResponseImpl EMPTY = new WriteResponseImpl( + ImmutableList.of(), ImmutableListMultimap.of()); + + private static final Logger log = getLogger(WriteResponseImpl.class); + + private final ImmutableList entityResponses; + private final ImmutableListMultimap statusMultimap; + + private WriteResponseImpl( + ImmutableList allResponses, + ImmutableListMultimap statusMultimap) { + this.entityResponses = allResponses; + this.statusMultimap = statusMultimap; + } + + @Override + public boolean isSuccess() { + return success().size() == all().size(); + } + + @Override + public Collection all() { + return entityResponses; + } + + @Override + public Collection success() { + return statusMultimap.get(WriteResponseStatus.OK); + } + + @Override + public Collection failed() { + return isSuccess() + ? Collections.emptyList() + : entityResponses.stream().filter(r -> !r.isSuccess()).collect(toList()); + } + + @Override + public Collection status( + WriteResponseStatus status) { + checkNotNull(status); + return statusMultimap.get(status); + } + + /** + * Returns a new response builder for the given device. + * + * @param deviceId device ID + * @return response builder + */ + static Builder builder(DeviceId deviceId) { + return new Builder(deviceId); + } + + /** + * Builder of P4RuntimeWriteResponseImpl. + */ + static final class Builder { + + private final DeviceId deviceId; + private final Map pendingResponses = + Maps.newHashMap(); + private final List allResponses = + Lists.newArrayList(); + private final ListMultimap statusMap = + ArrayListMultimap.create(); + + private Builder(DeviceId deviceId) { + this.deviceId = deviceId; + } + + void addPendingResponse(PiHandle handle, PiEntity entity, UpdateType updateType) { + synchronized (this) { + final WriteEntityResponseImpl resp = new WriteEntityResponseImpl( + handle, entity, updateType); + allResponses.add(resp); + pendingResponses.put(pendingResponses.size(), resp); + } + } + + void addFailedResponse(PiHandle handle, PiEntity entity, UpdateType updateType, + String explanation, WriteResponseStatus status) { + synchronized (this) { + final WriteEntityResponseImpl resp = new WriteEntityResponseImpl( + handle, entity, updateType) + .withFailure(explanation, status); + allResponses.add(resp); + } + } + + WriteResponseImpl buildAsIs() { + synchronized (this) { + if (!pendingResponses.isEmpty()) { + log.warn("Detected partial response from {}, " + + "{} of {} total entities are in status PENDING", + deviceId, pendingResponses.size(), allResponses.size()); + } + return new WriteResponseImpl( + ImmutableList.copyOf(allResponses), + ImmutableListMultimap.copyOf(statusMap)); + } + } + + WriteResponseImpl setSuccessAllAndBuild() { + synchronized (this) { + pendingResponses.values().forEach(this::doSetSuccess); + pendingResponses.clear(); + return buildAsIs(); + } + } + + WriteResponseImpl setErrorsAndBuild(Throwable throwable) { + synchronized (this) { + return doSetErrorsAndBuild(throwable); + } + } + + private void setSuccess(int index) { + synchronized (this) { + final WriteEntityResponseImpl resp = pendingResponses.remove(index); + if (resp != null) { + doSetSuccess(resp); + } else { + log.error("Missing pending response at index {}", index); + } + } + } + + private void doSetSuccess(WriteEntityResponseImpl resp) { + resp.setSuccess(); + statusMap.put(WriteResponseStatus.OK, resp); + } + + private void setFailure(int index, + String explanation, + WriteResponseStatus status) { + synchronized (this) { + final WriteEntityResponseImpl resp = pendingResponses.remove(index); + if (resp != null) { + resp.withFailure(explanation, status); + statusMap.put(status, resp); + log.warn("Unable to {} {} on {}: {} {} [{}]", + resp.updateType(), + resp.entityType().humanReadableName(), + deviceId, + status, explanation, + resp.entity() != null ? resp.entity() : resp.handle()); + } else { + log.error("Missing pending response at index {}", index); + } + } + } + + private WriteResponseImpl doSetErrorsAndBuild(Throwable throwable) { + if (!(throwable instanceof StatusRuntimeException)) { + // Leave all entity responses in pending state. + return buildAsIs(); + } + final StatusRuntimeException sre = (StatusRuntimeException) throwable; + if (!sre.getStatus().equals(Status.UNKNOWN)) { + // Error trailers expected only if status is UNKNOWN. + return buildAsIs(); + } + // Extract error details. + if (!sre.getTrailers().containsKey(STATUS_DETAILS_KEY)) { + log.warn("Cannot parse write error details from {}, " + + "missing status trailers in StatusRuntimeException", + deviceId); + return buildAsIs(); + } + com.google.rpc.Status status = sre.getTrailers().get(STATUS_DETAILS_KEY); + if (status == null) { + log.warn("Cannot parse write error details from {}, " + + "found NULL status trailers in StatusRuntimeException", + deviceId); + return buildAsIs(); + } + final boolean reconcilable = status.getDetailsList().size() == pendingResponses.size(); + // We expect one error for each entity... + if (!reconcilable) { + log.warn("Unable to reconcile write error details from {}, " + + "sent {} updates, but server returned {} errors", + deviceId, pendingResponses.size(), status.getDetailsList().size()); + } + // ...in the same order as in the request. + int index = 0; + for (Any any : status.getDetailsList()) { + // Set response entities only if reconcilable, otherwise log. + unpackP4Error(index, any, reconcilable); + index += 1; + } + return buildAsIs(); + } + + private void unpackP4Error(int index, Any any, boolean reconcilable) { + final P4RuntimeOuterClass.Error p4Error; + try { + p4Error = any.unpack(P4RuntimeOuterClass.Error.class); + } catch (InvalidProtocolBufferException e) { + final String unpackErr = format( + "P4Runtime Error message format not recognized [%s]", + TextFormat.shortDebugString(any)); + if (reconcilable) { + setFailure(index, unpackErr, WriteResponseStatus.OTHER_ERROR); + } else { + log.warn(unpackErr); + } + return; + } + // Map gRPC status codes to our WriteResponseStatus codes. + final Status.Code p4Code = Status.fromCodeValue( + p4Error.getCanonicalCode()).getCode(); + final WriteResponseStatus ourCode; + switch (p4Code) { + case OK: + if (reconcilable) { + setSuccess(index); + } + return; + case NOT_FOUND: + ourCode = WriteResponseStatus.NOT_FOUND; + break; + case ALREADY_EXISTS: + ourCode = WriteResponseStatus.ALREADY_EXIST; + break; + default: + ourCode = WriteResponseStatus.OTHER_ERROR; + break; + } + // Put the p4Code in the explanation only if ourCode is OTHER_ERROR. + final String explanationCode = ourCode == WriteResponseStatus.OTHER_ERROR + ? p4Code.name() + " " : ""; + final String details = p4Error.hasDetails() + ? ", " + p4Error.getDetails().toString() : ""; + final String explanation = format( + "%s%s%s (%s:%d)", explanationCode, p4Error.getMessage(), + details, p4Error.getSpace(), p4Error.getCode()); + if (reconcilable) { + setFailure(index, explanation, ourCode); + } else { + log.warn("P4Runtime write error: {}", explanation); + } + } + } + + /** + * Internal implementation of WriteEntityResponse. + */ + private static final class WriteEntityResponseImpl implements WriteEntityResponse { + + private final PiHandle handle; + private final PiEntity entity; + private final UpdateType updateType; + + private WriteResponseStatus status = WriteResponseStatus.PENDING; + private String explanation; + private Throwable throwable; + + private WriteEntityResponseImpl(PiHandle handle, PiEntity entity, UpdateType updateType) { + this.handle = handle; + this.entity = entity; + this.updateType = updateType; + } + + private WriteEntityResponseImpl withFailure( + String explanation, WriteResponseStatus status) { + this.status = status; + this.explanation = explanation; + this.throwable = null; + return this; + } + + private void setSuccess() { + this.status = WriteResponseStatus.OK; + } + + @Override + public PiHandle handle() { + return handle; + } + + @Override + public PiEntity entity() { + return entity; + } + + @Override + public UpdateType updateType() { + return updateType; + } + + @Override + public PiEntityType entityType() { + return handle.entityType(); + } + + @Override + public boolean isSuccess() { + return status().equals(WriteResponseStatus.OK); + } + + @Override + public WriteResponseStatus status() { + return status; + } + + @Override + public String explanation() { + return explanation; + } + + @Override + public Throwable throwable() { + return throwable; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("handle", handle) + .add("entity", entity) + .add("updateType", updateType) + .add("status", status) + .add("explanation", explanation) + .add("throwable", throwable) + .toString(); + } + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/package-info.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/package-info.java new file mode 100644 index 0000000000..a5614a3e4b --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2019-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. + */ + +/** + * P4Runtime client implementation classes. + */ +package org.onosproject.p4runtime.ctl.client; diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/AbstractCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/AbstractCodec.java new file mode 100644 index 0000000000..2bb75a30e6 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/AbstractCodec.java @@ -0,0 +1,258 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Message; +import com.google.protobuf.TextFormat; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import org.onosproject.p4runtime.ctl.utils.PipeconfHelper; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Abstract implementation of a general codec that translates pipeconf-related + * objects into protobuf messages and vice versa. + * + * @param

object + * @param protobuf message class + * @param metadata class + */ +abstract class AbstractCodec { + + protected final Logger log = getLogger(this.getClass()); + + protected abstract M encode(P object, X metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException; + + protected abstract P decode(M message, X metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException; + + /** + * Returns a protobuf message that is equivalent to the given object for the + * given metadata and pipeconf. + * + * @param object object + * @param metadata metadata + * @param pipeconf pipeconf + * @return protobuf message + * @throws CodecException if the given object cannot be encoded (see + * exception message) + */ + public M encode(P object, X metadata, PiPipeconf pipeconf) + throws CodecException { + checkNotNull(object); + try { + return encode(object, metadata, pipeconf, browserOrFail(pipeconf)); + } catch (P4InfoBrowser.NotFoundException e) { + throw new CodecException(e.getMessage()); + } + } + + /** + * Returns a object that is equivalent to the protobuf message for the given + * metadata and pipeconf. + * + * @param message protobuf message + * @param metadata metadata + * @param pipeconf pipeconf pipeconf + * @return object + * @throws CodecException if the given protobuf message cannot be decoded + * (see exception message) + */ + public P decode(M message, X metadata, PiPipeconf pipeconf) + throws CodecException { + checkNotNull(message); + try { + return decode(message, metadata, pipeconf, browserOrFail(pipeconf)); + } catch (P4InfoBrowser.NotFoundException e) { + throw new CodecException(e.getMessage()); + } + } + + /** + * Same as {@link #encode(Object, Object, PiPipeconf)} but returns null in + * case of exceptions, while the error message is logged. + * + * @param object object + * @param metadata metadata + * @param pipeconf pipeconf + * @return protobuf message + */ + private M encodeOrNull(P object, X metadata, PiPipeconf pipeconf) { + checkNotNull(object); + try { + return encode(object, metadata, pipeconf); + } catch (CodecException e) { + log.error("Unable to encode {}: {} [{}]", + object.getClass().getSimpleName(), + e.getMessage(), object.toString()); + return null; + } + } + + /** + * Same as {@link #decode(Message, Object, PiPipeconf)} but returns null in + * case of exceptions, while the error message is logged. + * + * @param message protobuf message + * @param metadata metadata + * @param pipeconf pipeconf pipeconf + * @return object + */ + private P decodeOrNull(M message, X metadata, PiPipeconf pipeconf) { + checkNotNull(message); + try { + return decode(message, metadata, pipeconf); + } catch (CodecException e) { + log.error("Unable to decode {}: {} [{}]", + message.getClass().getSimpleName(), + e.getMessage(), TextFormat.shortDebugString(message)); + return null; + } + } + + /** + * Encodes the given list of objects, skipping those that cannot be encoded, + * in which case an error message is logged. For this reason, the returned + * list might have different size than the returned one. + * + * @param objects list of objects + * @param metadata metadata + * @param pipeconf pipeconf + * @return list of protobuf messages + */ + private List encodeAllSkipException( + Collection

objects, X metadata, PiPipeconf pipeconf) { + checkNotNull(objects); + if (objects.isEmpty()) { + return ImmutableList.of(); + } + return objects.stream() + .map(p -> encodeOrNull(p, metadata, pipeconf)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * Decodes the given list of protobuf messages, skipping those that cannot + * be decoded, on which case an error message is logged. For this reason, + * the returned list might have different size than the returned one. + * + * @param messages list of protobuf messages + * @param metadata metadata + * @param pipeconf pipeconf + * @return list of objects + */ + private List

decodeAllSkipException( + Collection messages, X metadata, PiPipeconf pipeconf) { + checkNotNull(messages); + if (messages.isEmpty()) { + return ImmutableList.of(); + } + return messages.stream() + .map(m -> decodeOrNull(m, metadata, pipeconf)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * Encodes the given collection of objects. Throws an exception if one or + * more of the given objects cannot be encoded. The returned list is + * guaranteed to have same size and order as the given one. + * + * @param objects list of objects + * @param metadata metadata + * @param pipeconf pipeconf + * @return list of protobuf messages + * @throws CodecException if one or more of the given objects cannot be + * encoded + */ + List encodeAll(Collection

objects, X metadata, PiPipeconf pipeconf) + throws CodecException { + checkNotNull(objects); + if (objects.isEmpty()) { + return ImmutableList.of(); + } + final List messages = encodeAllSkipException(objects, metadata, pipeconf); + if (objects.size() != messages.size()) { + throw new CodecException(format( + "Unable to encode %d entities of %d given " + + "(see previous logs for details)", + objects.size() - messages.size(), objects.size())); + } + return messages; + } + + /** + * Decodes the given collection of protobuf messages. Throws an exception if + * one or more of the given protobuf messages cannot be decoded. The + * returned list is guaranteed to have same size and order as the given + * one. + * + * @param messages list of protobuf messages + * @param metadata metadata + * @param pipeconf pipeconf + * @return list of objects + * @throws CodecException if one or more of the given protobuf messages + * cannot be decoded + */ + List

decodeAll(Collection messages, X metadata, PiPipeconf pipeconf) + throws CodecException { + checkNotNull(messages); + if (messages.isEmpty()) { + return ImmutableList.of(); + } + final List

objects = decodeAllSkipException(messages, metadata, pipeconf); + if (messages.size() != objects.size()) { + throw new CodecException(format( + "Unable to decode %d messages of %d given " + + "(see previous logs for details)", + messages.size() - objects.size(), messages.size())); + } + return objects; + } + + /** + * Returns a P4Info browser for the given pipeconf or throws a + * CodecException if not possible. + * + * @param pipeconf pipeconf + * @return P4Info browser + * @throws CodecException if a P4Info browser cannot be obtained + */ + P4InfoBrowser browserOrFail(PiPipeconf pipeconf) throws CodecException { + checkNotNull(pipeconf); + final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); + if (browser == null) { + throw new CodecException(format( + "Unable to get P4InfoBrowser for pipeconf %s", pipeconf.id())); + } + return browser; + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/AbstractEntityCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/AbstractEntityCodec.java new file mode 100644 index 0000000000..0b39367d3e --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/AbstractEntityCodec.java @@ -0,0 +1,91 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import com.google.protobuf.Message; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiEntity; +import org.onosproject.net.pi.runtime.PiHandle; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Abstract implementation of a specialized codec that translates PI runtime + * entities and their handles into P4Runtime protobuf messages and vice versa. + * Supports also encoding to "key" P4Runtime Entity messages used in read and + * delete operations. + * + * @param

PI runtime class + * @param PI handle class + * @param P4Runtime protobuf message class + * @param metadata class + */ +public abstract class AbstractEntityCodec +

+ extends AbstractCodec { + + protected abstract M encodeKey(H handle, X metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException; + + protected abstract M encodeKey(P piEntity, X metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException; + + /** + * Returns a P4Runtime protobuf message representing the P4Runtime.Entity + * "key" for the given PI handle, metadata and pipeconf. + * + * @param handle PI handle instance + * @param metadata metadata + * @param pipeconf pipeconf + * @return P4Runtime protobuf message + * @throws CodecException if the given PI entity cannot be encoded (see + * exception message) + */ + public M encodeKey(H handle, X metadata, PiPipeconf pipeconf) + throws CodecException { + checkNotNull(handle); + try { + return encodeKey(handle, metadata, pipeconf, browserOrFail(pipeconf)); + } catch (P4InfoBrowser.NotFoundException e) { + throw new CodecException(e.getMessage()); + } + } + + /** + * Returns a P4Runtime protobuf message representing the P4Runtime.Entity + * "key" for the given PI entity, metadata and pipeconf. + * + * @param piEntity PI entity instance + * @param metadata metadata + * @param pipeconf pipeconf + * @return P4Runtime protobuf message + * @throws CodecException if the given PI entity cannot be encoded (see + * exception message) + */ + public M encodeKey(P piEntity, X metadata, PiPipeconf pipeconf) + throws CodecException { + checkNotNull(piEntity); + try { + return encodeKey(piEntity, metadata, pipeconf, browserOrFail(pipeconf)); + } catch (P4InfoBrowser.NotFoundException e) { + throw new CodecException(e.getMessage()); + } + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionCodec.java new file mode 100644 index 0000000000..3ae46c9c8f --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionCodec.java @@ -0,0 +1,81 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import com.google.protobuf.ByteString; +import org.onlab.util.ImmutableByteSequence; +import org.onosproject.net.pi.model.PiActionId; +import org.onosproject.net.pi.model.PiActionParamId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiAction; +import org.onosproject.net.pi.runtime.PiActionParam; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.config.v1.P4InfoOuterClass; +import p4.v1.P4RuntimeOuterClass; + +import static java.lang.String.format; +import static org.onosproject.p4runtime.ctl.codec.Utils.assertSize; + +/** + * Codec for P4Runtime Action. + */ +public final class ActionCodec + extends AbstractCodec { + + @Override + protected P4RuntimeOuterClass.Action encode( + PiAction piAction, Object ignored, PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException { + final int actionId = browser.actions() + .getByName(piAction.id().toString()).getPreamble().getId(); + final P4RuntimeOuterClass.Action.Builder actionMsgBuilder = + P4RuntimeOuterClass.Action.newBuilder().setActionId(actionId); + for (PiActionParam p : piAction.parameters()) { + final P4InfoOuterClass.Action.Param paramInfo = browser.actionParams(actionId) + .getByName(p.id().toString()); + final ByteString paramValue = ByteString.copyFrom(p.value().asReadOnlyBuffer()); + assertSize(format("param '%s' of action '%s'", p.id(), piAction.id()), + paramValue, paramInfo.getBitwidth()); + actionMsgBuilder.addParams(P4RuntimeOuterClass.Action.Param.newBuilder() + .setParamId(paramInfo.getId()) + .setValue(paramValue) + .build()); + } + return actionMsgBuilder.build(); + } + + @Override + protected PiAction decode( + P4RuntimeOuterClass.Action message, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + final P4InfoBrowser.EntityBrowser paramInfo = + browser.actionParams(message.getActionId()); + final String actionName = browser.actions() + .getById(message.getActionId()) + .getPreamble().getName(); + final PiAction.Builder builder = PiAction.builder() + .withId(PiActionId.of(actionName)); + for (P4RuntimeOuterClass.Action.Param p : message.getParamsList()) { + final String paramName = paramInfo.getById(p.getParamId()).getName(); + final ImmutableByteSequence value = ImmutableByteSequence.copyFrom( + p.getValue().toByteArray()); + builder.withParameter(new PiActionParam(PiActionParamId.of(paramName), value)); + } + return builder.build(); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionProfileGroupCodec.java similarity index 54% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupCodec.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionProfileGroupCodec.java index 684ef04196..fc74df79f6 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupCodec.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionProfileGroupCodec.java @@ -14,32 +14,30 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.codec; import org.onosproject.net.pi.model.PiActionProfileId; import org.onosproject.net.pi.model.PiPipeconf; import org.onosproject.net.pi.runtime.PiActionProfileGroup; +import org.onosproject.net.pi.runtime.PiActionProfileGroupHandle; import org.onosproject.net.pi.runtime.PiActionProfileGroupId; import org.onosproject.net.pi.runtime.PiActionProfileMemberId; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; import p4.v1.P4RuntimeOuterClass.ActionProfileGroup; /** * Codec for P4Runtime ActionProfileGroup. */ -final class ActionProfileGroupCodec - extends AbstractP4RuntimeCodec { +public final class ActionProfileGroupCodec + extends AbstractEntityCodec { @Override public ActionProfileGroup encode( - PiActionProfileGroup piGroup, PiPipeconf pipeconf, P4InfoBrowser browser) + PiActionProfileGroup piGroup, Object ignored, PiPipeconf pipeconf, + P4InfoBrowser browser) throws P4InfoBrowser.NotFoundException { - - final int p4ActionProfileId = browser.actionProfiles() - .getByName(piGroup.actionProfile().id()) - .getPreamble().getId(); - final ActionProfileGroup.Builder msgBuilder = ActionProfileGroup.newBuilder() - .setGroupId(piGroup.id().id()) - .setActionProfileId(p4ActionProfileId) + final ActionProfileGroup.Builder msgBuilder = keyMsgBuilder( + piGroup.actionProfile(), piGroup.id(), browser) .setMaxSize(piGroup.maxSize()); piGroup.members().forEach(m -> { // TODO: currently we don't set "watch" field @@ -49,15 +47,43 @@ final class ActionProfileGroupCodec .build(); msgBuilder.addMembers(member); }); - return msgBuilder.build(); } @Override - public PiActionProfileGroup decode( - ActionProfileGroup msg, PiPipeconf pipeconf, P4InfoBrowser browser) - throws CodecException, P4InfoBrowser.NotFoundException { + protected ActionProfileGroup encodeKey( + PiActionProfileGroupHandle handle, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + return keyMsgBuilder(handle.actionProfile(), handle.groupId(), browser) + .build(); + } + @Override + protected ActionProfileGroup encodeKey( + PiActionProfileGroup piEntity, Object metadata, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + return keyMsgBuilder(piEntity.actionProfile(), piEntity.id(), browser) + .build(); + } + + private ActionProfileGroup.Builder keyMsgBuilder( + PiActionProfileId actProfId, PiActionProfileGroupId groupId, + P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + return ActionProfileGroup.newBuilder() + .setGroupId(groupId.id()) + .setActionProfileId(browser.actionProfiles() + .getByName(actProfId.id()) + .getPreamble().getId()); + } + + @Override + public PiActionProfileGroup decode( + ActionProfileGroup msg, Object ignored, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { final PiActionProfileGroup.Builder piGroupBuilder = PiActionProfileGroup.builder() .withActionProfileId(PiActionProfileId.of( browser.actionProfiles() @@ -65,13 +91,12 @@ final class ActionProfileGroupCodec .getPreamble().getName())) .withId(PiActionProfileGroupId.of(msg.getGroupId())) .withMaxSize(msg.getMaxSize()); - msg.getMembersList().forEach(m -> { int weight = m.getWeight(); if (weight < 1) { - // FIXME: currently PI has a bug which will always return weight 0 + // FIXME: PI has a bug which will always return weight 0 // ONOS won't accept group buckets with weight 0 - log.warn("Decoding ActionProfileGroup with 'weight' " + + log.debug("Decoding ActionProfileGroup with 'weight' " + "field {}, will set to 1", weight); weight = 1; } diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionProfileMemberCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionProfileMemberCodec.java new file mode 100644 index 0000000000..183d827c48 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/ActionProfileMemberCodec.java @@ -0,0 +1,95 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import org.onosproject.net.pi.model.PiActionProfileId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiActionProfileMember; +import org.onosproject.net.pi.runtime.PiActionProfileMemberHandle; +import org.onosproject.net.pi.runtime.PiActionProfileMemberId; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.v1.P4RuntimeOuterClass; +import p4.v1.P4RuntimeOuterClass.ActionProfileMember; + +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; + +/** + * Codec for ActionProfileMember. + */ +public final class ActionProfileMemberCodec + extends AbstractEntityCodec { + + @Override + public ActionProfileMember encode( + PiActionProfileMember piEntity, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException { + return keyMsgBuilder( + piEntity.actionProfile(), piEntity.id(), browser) + .setAction(CODECS.action().encode( + piEntity.action(), null, pipeconf)) + .build(); + } + + @Override + protected ActionProfileMember encodeKey( + PiActionProfileMemberHandle handle, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + return keyMsgBuilder(handle.actionProfileId(), handle.memberId(), browser) + .build(); + } + + @Override + protected ActionProfileMember encodeKey( + PiActionProfileMember piEntity, Object metadata, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + return keyMsgBuilder( + piEntity.actionProfile(), piEntity.id(), browser) + .build(); + } + + private P4RuntimeOuterClass.ActionProfileMember.Builder keyMsgBuilder( + PiActionProfileId actProfId, PiActionProfileMemberId memberId, + P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + return P4RuntimeOuterClass.ActionProfileMember.newBuilder() + .setActionProfileId(browser.actionProfiles() + .getByName(actProfId.id()) + .getPreamble().getId()) + .setMemberId(memberId.id()); + } + + @Override + public PiActionProfileMember decode( + ActionProfileMember message, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException, CodecException { + final PiActionProfileId actionProfileId = PiActionProfileId.of( + browser.actionProfiles() + .getById(message.getActionProfileId()) + .getPreamble() + .getName()); + return PiActionProfileMember.builder() + .forActionProfile(actionProfileId) + .withId(PiActionProfileMemberId.of(message.getMemberId())) + .withAction(CODECS.action().decode( + message.getAction(), null, pipeconf)) + .build(); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CodecException.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/CodecException.java similarity index 88% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CodecException.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/CodecException.java index 89d5510c48..6ef77f2a50 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CodecException.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/CodecException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.codec; /** * Signals an error during encoding/decoding of a PI entity/protobuf message. @@ -22,7 +22,8 @@ package org.onosproject.p4runtime.ctl; public final class CodecException extends Exception { /** - * Ceeates anew exception with the given explanation message. + * Creates a new exception with the given explanation message. + * * @param explanation explanation */ public CodecException(String explanation) { diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/Codecs.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/Codecs.java new file mode 100644 index 0000000000..771f5dadb4 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/Codecs.java @@ -0,0 +1,119 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +/** + * Utility class that provides access to P4Runtime codec instances. + */ +public final class Codecs { + + public static final Codecs CODECS = new Codecs(); + + private final ActionCodec action; + private final ActionProfileGroupCodec actionProfileGroup; + private final ActionProfileMemberCodec actionProfileMember; + private final CounterEntryCodec counterEntry; + private final DirectCounterEntryCodec directCounterEntry; + private final DirectMeterEntryCodec directMeterEntry; + private final EntityCodec entity; + private final FieldMatchCodec fieldMatch; + private final HandleCodec handle; + private final MeterEntryCodec meterEntry; + private final MulticastGroupEntryCodec multicastGroupEntry; + private final PacketInCodec packetIn; + private final PacketMetadataCodec packetMetadata; + private final PacketOutCodec packetOut; + private final TableEntryCodec tableEntry; + + private Codecs() { + this.action = new ActionCodec(); + this.actionProfileGroup = new ActionProfileGroupCodec(); + this.actionProfileMember = new ActionProfileMemberCodec(); + this.counterEntry = new CounterEntryCodec(); + this.directCounterEntry = new DirectCounterEntryCodec(); + this.directMeterEntry = new DirectMeterEntryCodec(); + this.entity = new EntityCodec(); + this.fieldMatch = new FieldMatchCodec(); + this.handle = new HandleCodec(); + this.meterEntry = new MeterEntryCodec(); + this.multicastGroupEntry = new MulticastGroupEntryCodec(); + this.packetIn = new PacketInCodec(); + this.packetMetadata = new PacketMetadataCodec(); + this.packetOut = new PacketOutCodec(); + this.tableEntry = new TableEntryCodec(); + } + + public EntityCodec entity() { + return entity; + } + + public HandleCodec handle() { + return handle; + } + + public PacketOutCodec packetOut() { + return packetOut; + } + + public PacketInCodec packetIn() { + return packetIn; + } + + TableEntryCodec tableEntry() { + return tableEntry; + } + + FieldMatchCodec fieldMatch() { + return fieldMatch; + } + + ActionCodec action() { + return action; + } + + ActionProfileMemberCodec actionProfileMember() { + return actionProfileMember; + } + + ActionProfileGroupCodec actionProfileGroup() { + return actionProfileGroup; + } + + PacketMetadataCodec packetMetadata() { + return packetMetadata; + } + + MulticastGroupEntryCodec multicastGroupEntry() { + return multicastGroupEntry; + } + + DirectMeterEntryCodec directMeterEntry() { + return directMeterEntry; + } + + MeterEntryCodec meterEntry() { + return meterEntry; + } + + CounterEntryCodec counterEntry() { + return counterEntry; + } + + DirectCounterEntryCodec directCounterEntry() { + return directCounterEntry; + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/CounterEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/CounterEntryCodec.java new file mode 100644 index 0000000000..deac99347c --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/CounterEntryCodec.java @@ -0,0 +1,89 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import org.onosproject.net.pi.model.PiCounterId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiCounterCell; +import org.onosproject.net.pi.runtime.PiCounterCellHandle; +import org.onosproject.net.pi.runtime.PiCounterCellId; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.v1.P4RuntimeOuterClass; + +/** + * Codec for P4Runtime CounterEntry. + */ +public final class CounterEntryCodec + extends AbstractEntityCodec { + + @Override + protected P4RuntimeOuterClass.CounterEntry encode( + PiCounterCell piEntity, Object ignored, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + return keyMsgBuilder(piEntity.cellId(), browser) + .setData(P4RuntimeOuterClass.CounterData.newBuilder() + .setByteCount(piEntity.data().bytes()) + .setPacketCount(piEntity.data().packets()) + .build()) + .build(); + } + + @Override + protected P4RuntimeOuterClass.CounterEntry encodeKey( + PiCounterCellHandle handle, Object metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + return keyMsgBuilder(handle.cellId(), browser).build(); + } + + @Override + protected P4RuntimeOuterClass.CounterEntry encodeKey( + PiCounterCell piEntity, Object metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + return keyMsgBuilder(piEntity.cellId(), browser).build(); + } + + private P4RuntimeOuterClass.CounterEntry.Builder keyMsgBuilder( + PiCounterCellId cellId, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + final int counterId = browser.counters().getByName( + cellId.counterId().id()).getPreamble().getId(); + return P4RuntimeOuterClass.CounterEntry.newBuilder() + .setCounterId(counterId) + .setIndex(P4RuntimeOuterClass.Index.newBuilder() + .setIndex(cellId.index()).build()); + } + + @Override + protected PiCounterCell decode( + P4RuntimeOuterClass.CounterEntry message, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + final String counterName = browser.counters() + .getById(message.getCounterId()) + .getPreamble() + .getName(); + return new PiCounterCell( + PiCounterCellId.ofIndirect( + PiCounterId.of(counterName), message.getIndex().getIndex()), + message.getData().getPacketCount(), + message.getData().getByteCount()); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/DirectCounterEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/DirectCounterEntryCodec.java new file mode 100644 index 0000000000..46d0f3f55d --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/DirectCounterEntryCodec.java @@ -0,0 +1,84 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiCounterCell; +import org.onosproject.net.pi.runtime.PiCounterCellHandle; +import org.onosproject.net.pi.runtime.PiCounterCellId; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.v1.P4RuntimeOuterClass; + +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; + +/** + * Codec for P4Runtime DirectCounterEntryCodec. + */ +public final class DirectCounterEntryCodec + extends AbstractEntityCodec { + + @Override + protected P4RuntimeOuterClass.DirectCounterEntry encode( + PiCounterCell piEntity, Object ignored, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException { + return keyMsgBuilder(piEntity.cellId(), pipeconf) + .setData(P4RuntimeOuterClass.CounterData.newBuilder() + .setByteCount(piEntity.data().bytes()) + .setPacketCount(piEntity.data().packets()) + .build()) + .build(); + } + + @Override + protected P4RuntimeOuterClass.DirectCounterEntry encodeKey( + PiCounterCellHandle handle, Object metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException { + return keyMsgBuilder(handle.cellId(), pipeconf).build(); + } + + @Override + protected P4RuntimeOuterClass.DirectCounterEntry encodeKey( + PiCounterCell piEntity, Object metadata, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException { + return keyMsgBuilder(piEntity.cellId(), pipeconf).build(); + } + + private P4RuntimeOuterClass.DirectCounterEntry.Builder keyMsgBuilder( + PiCounterCellId cellId, PiPipeconf pipeconf) + throws CodecException { + return P4RuntimeOuterClass.DirectCounterEntry.newBuilder() + .setTableEntry(CODECS.tableEntry().encodeKey( + cellId.tableEntry(), null, pipeconf)); + } + + @Override + protected PiCounterCell decode( + P4RuntimeOuterClass.DirectCounterEntry message, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException { + return new PiCounterCell( + PiCounterCellId.ofDirect( + CODECS.tableEntry().decode( + message.getTableEntry(), null, pipeconf)), + message.getData().getPacketCount(), + message.getData().getByteCount()); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/DirectMeterEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/DirectMeterEntryCodec.java new file mode 100644 index 0000000000..3bedfbf411 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/DirectMeterEntryCodec.java @@ -0,0 +1,87 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiMeterBand; +import org.onosproject.net.pi.runtime.PiMeterCellConfig; +import org.onosproject.net.pi.runtime.PiMeterCellHandle; +import org.onosproject.net.pi.runtime.PiMeterCellId; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.v1.P4RuntimeOuterClass; + +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; + +/** + * Codec for P4Runtime DirectMeterEntryCodec. + */ +public final class DirectMeterEntryCodec + extends AbstractEntityCodec { + + @Override + protected P4RuntimeOuterClass.DirectMeterEntry encode( + PiMeterCellConfig piEntity, Object ignored, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException { + return P4RuntimeOuterClass.DirectMeterEntry.newBuilder() + .setTableEntry(CODECS.tableEntry().encode( + piEntity.cellId().tableEntry(), null, pipeconf)) + .setConfig(MeterEntryCodec.getP4Config(piEntity)) + .build(); + } + + @Override + protected P4RuntimeOuterClass.DirectMeterEntry encodeKey( + PiMeterCellHandle handle, Object metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException { + return keyMsgBuilder(handle.cellId(), pipeconf).build(); + } + + @Override + protected P4RuntimeOuterClass.DirectMeterEntry encodeKey( + PiMeterCellConfig piEntity, Object metadata, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException { + return keyMsgBuilder(piEntity.cellId(), pipeconf).build(); + } + + private P4RuntimeOuterClass.DirectMeterEntry.Builder keyMsgBuilder( + PiMeterCellId cellId, PiPipeconf pipeconf) + throws CodecException { + return P4RuntimeOuterClass.DirectMeterEntry.newBuilder() + .setTableEntry(CODECS.tableEntry().encodeKey( + cellId.tableEntry(), null, pipeconf)); + } + + @Override + protected PiMeterCellConfig decode( + P4RuntimeOuterClass.DirectMeterEntry message, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException { + return PiMeterCellConfig.builder() + .withMeterCellId(PiMeterCellId.ofDirect( + CODECS.tableEntry().decode( + message.getTableEntry(), null, pipeconf))) + .withMeterBand(new PiMeterBand(message.getConfig().getCir(), + message.getConfig().getCburst())) + .withMeterBand(new PiMeterBand(message.getConfig().getPir(), + message.getConfig().getPburst())) + .build(); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/EntityCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/EntityCodec.java new file mode 100644 index 0000000000..dc104195e6 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/EntityCodec.java @@ -0,0 +1,165 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiActionProfileGroup; +import org.onosproject.net.pi.runtime.PiActionProfileMember; +import org.onosproject.net.pi.runtime.PiCounterCell; +import org.onosproject.net.pi.runtime.PiEntity; +import org.onosproject.net.pi.runtime.PiMeterCellConfig; +import org.onosproject.net.pi.runtime.PiMulticastGroupEntry; +import org.onosproject.net.pi.runtime.PiTableEntry; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.v1.P4RuntimeOuterClass; + +import static java.lang.String.format; +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; + +/** + * Codec for P4Runtime Entity. + */ +public final class EntityCodec extends AbstractCodec { + + @Override + protected P4RuntimeOuterClass.Entity encode( + PiEntity piEntity, Object ignored, PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException { + final P4RuntimeOuterClass.Entity.Builder p4Entity = P4RuntimeOuterClass.Entity.newBuilder(); + switch (piEntity.piEntityType()) { + case TABLE_ENTRY: + return p4Entity.setTableEntry( + CODECS.tableEntry().encode( + (PiTableEntry) piEntity, null, pipeconf)) + .build(); + case ACTION_PROFILE_GROUP: + return p4Entity.setActionProfileGroup( + CODECS.actionProfileGroup().encode( + (PiActionProfileGroup) piEntity, null, pipeconf)) + .build(); + case ACTION_PROFILE_MEMBER: + return p4Entity.setActionProfileMember( + CODECS.actionProfileMember().encode( + (PiActionProfileMember) piEntity, null, pipeconf)) + .build(); + case PRE_MULTICAST_GROUP_ENTRY: + return p4Entity.setPacketReplicationEngineEntry( + P4RuntimeOuterClass.PacketReplicationEngineEntry.newBuilder() + .setMulticastGroupEntry(CODECS.multicastGroupEntry().encode( + (PiMulticastGroupEntry) piEntity, null, pipeconf)) + .build()) + .build(); + case METER_CELL_CONFIG: + final PiMeterCellConfig meterCellConfig = (PiMeterCellConfig) piEntity; + switch (meterCellConfig.cellId().meterType()) { + case DIRECT: + return p4Entity.setDirectMeterEntry( + CODECS.directMeterEntry().encode( + meterCellConfig, null, pipeconf)) + .build(); + case INDIRECT: + return p4Entity.setMeterEntry( + CODECS.meterEntry().encode( + meterCellConfig, null, pipeconf)) + .build(); + default: + throw new CodecException(format( + "Encoding of %s of type %s is not supported", + piEntity.piEntityType(), + meterCellConfig.cellId().meterType())); + } + case COUNTER_CELL: + final PiCounterCell counterCell = (PiCounterCell) piEntity; + switch (counterCell.cellId().counterType()) { + case DIRECT: + return p4Entity.setDirectCounterEntry( + CODECS.directCounterEntry().encode( + counterCell, null, pipeconf)) + .build(); + case INDIRECT: + return p4Entity.setCounterEntry( + CODECS.counterEntry().encode( + counterCell, null, pipeconf)) + .build(); + default: + throw new CodecException(format( + "Encoding of %s of type %s is not supported", + piEntity.piEntityType(), + counterCell.cellId().counterType())); + } + case REGISTER_CELL: + case PRE_CLONE_SESSION_ENTRY: + default: + throw new CodecException(format( + "Encoding of %s not supported", + piEntity.piEntityType())); + } + } + + @Override + protected PiEntity decode( + P4RuntimeOuterClass.Entity message, Object ignored, PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException { + switch (message.getEntityCase()) { + case TABLE_ENTRY: + return CODECS.tableEntry().decode( + message.getTableEntry(), null, pipeconf); + case ACTION_PROFILE_MEMBER: + return CODECS.actionProfileMember().decode( + message.getActionProfileMember(), null, pipeconf); + case ACTION_PROFILE_GROUP: + return CODECS.actionProfileGroup().decode( + message.getActionProfileGroup(), null, pipeconf); + case METER_ENTRY: + return CODECS.meterEntry().decode( + message.getMeterEntry(), null, pipeconf); + case DIRECT_METER_ENTRY: + return CODECS.directMeterEntry().decode( + message.getDirectMeterEntry(), null, pipeconf); + case COUNTER_ENTRY: + return CODECS.counterEntry().decode( + message.getCounterEntry(), null, pipeconf); + case DIRECT_COUNTER_ENTRY: + return CODECS.directCounterEntry().decode( + message.getDirectCounterEntry(), null, pipeconf); + case PACKET_REPLICATION_ENGINE_ENTRY: + switch (message.getPacketReplicationEngineEntry().getTypeCase()) { + case MULTICAST_GROUP_ENTRY: + return CODECS.multicastGroupEntry().decode( + message.getPacketReplicationEngineEntry() + .getMulticastGroupEntry(), null, pipeconf); + case CLONE_SESSION_ENTRY: + case TYPE_NOT_SET: + default: + throw new CodecException(format( + "Decoding of %s of type %s not supported", + message.getEntityCase(), + message.getPacketReplicationEngineEntry().getTypeCase())); + } + case VALUE_SET_ENTRY: + case REGISTER_ENTRY: + case DIGEST_ENTRY: + case EXTERN_ENTRY: + case ENTITY_NOT_SET: + default: + throw new CodecException(format( + "Decoding of %s not supported", + message.getEntityCase())); + + } + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/FieldMatchCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/FieldMatchCodec.java new file mode 100644 index 0000000000..f289d5d890 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/FieldMatchCodec.java @@ -0,0 +1,161 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import com.google.protobuf.ByteString; +import org.onlab.util.ImmutableByteSequence; +import org.onosproject.net.pi.model.PiMatchFieldId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiExactFieldMatch; +import org.onosproject.net.pi.runtime.PiFieldMatch; +import org.onosproject.net.pi.runtime.PiLpmFieldMatch; +import org.onosproject.net.pi.runtime.PiRangeFieldMatch; +import org.onosproject.net.pi.runtime.PiTernaryFieldMatch; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.config.v1.P4InfoOuterClass; +import p4.v1.P4RuntimeOuterClass; + +import static java.lang.String.format; +import static org.onlab.util.ImmutableByteSequence.copyFrom; +import static org.onosproject.p4runtime.ctl.codec.Utils.assertPrefixLen; +import static org.onosproject.p4runtime.ctl.codec.Utils.assertSize; + +/** + * Codec for P4Runtime FieldMatch. Metadata is expected to be a Preamble for + * P4Info.Table. + */ +public final class FieldMatchCodec + extends AbstractCodec { + + private static final String VALUE_OF_PREFIX = "value of "; + private static final String MASK_OF_PREFIX = "mask of "; + private static final String HIGH_RANGE_VALUE_OF_PREFIX = "high range value of "; + private static final String LOW_RANGE_VALUE_OF_PREFIX = "low range value of "; + + @Override + public P4RuntimeOuterClass.FieldMatch encode( + PiFieldMatch piFieldMatch, P4InfoOuterClass.Preamble tablePreamble, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException { + + P4RuntimeOuterClass.FieldMatch.Builder messageBuilder = P4RuntimeOuterClass + .FieldMatch.newBuilder(); + + // FIXME: check how field names for stacked headers are constructed in P4Runtime. + String fieldName = piFieldMatch.fieldId().id(); + P4InfoOuterClass.MatchField matchFieldInfo = browser.matchFields( + tablePreamble.getId()).getByName(fieldName); + String entityName = format("field match '%s' of table '%s'", + matchFieldInfo.getName(), tablePreamble.getName()); + int fieldId = matchFieldInfo.getId(); + int fieldBitwidth = matchFieldInfo.getBitwidth(); + + messageBuilder.setFieldId(fieldId); + + switch (piFieldMatch.type()) { + case EXACT: + PiExactFieldMatch fieldMatch = (PiExactFieldMatch) piFieldMatch; + ByteString exactValue = ByteString.copyFrom(fieldMatch.value().asReadOnlyBuffer()); + assertSize(VALUE_OF_PREFIX + entityName, exactValue, fieldBitwidth); + return messageBuilder.setExact( + P4RuntimeOuterClass.FieldMatch.Exact + .newBuilder() + .setValue(exactValue) + .build()) + .build(); + case TERNARY: + PiTernaryFieldMatch ternaryMatch = (PiTernaryFieldMatch) piFieldMatch; + ByteString ternaryValue = ByteString.copyFrom(ternaryMatch.value().asReadOnlyBuffer()); + ByteString ternaryMask = ByteString.copyFrom(ternaryMatch.mask().asReadOnlyBuffer()); + assertSize(VALUE_OF_PREFIX + entityName, ternaryValue, fieldBitwidth); + assertSize(MASK_OF_PREFIX + entityName, ternaryMask, fieldBitwidth); + return messageBuilder.setTernary( + P4RuntimeOuterClass.FieldMatch.Ternary + .newBuilder() + .setValue(ternaryValue) + .setMask(ternaryMask) + .build()) + .build(); + case LPM: + PiLpmFieldMatch lpmMatch = (PiLpmFieldMatch) piFieldMatch; + ByteString lpmValue = ByteString.copyFrom(lpmMatch.value().asReadOnlyBuffer()); + int lpmPrefixLen = lpmMatch.prefixLength(); + assertSize(VALUE_OF_PREFIX + entityName, lpmValue, fieldBitwidth); + assertPrefixLen(entityName, lpmPrefixLen, fieldBitwidth); + return messageBuilder.setLpm( + P4RuntimeOuterClass.FieldMatch.LPM.newBuilder() + .setValue(lpmValue) + .setPrefixLen(lpmPrefixLen) + .build()) + .build(); + case RANGE: + PiRangeFieldMatch rangeMatch = (PiRangeFieldMatch) piFieldMatch; + ByteString rangeHighValue = ByteString.copyFrom(rangeMatch.highValue().asReadOnlyBuffer()); + ByteString rangeLowValue = ByteString.copyFrom(rangeMatch.lowValue().asReadOnlyBuffer()); + assertSize(HIGH_RANGE_VALUE_OF_PREFIX + entityName, rangeHighValue, fieldBitwidth); + assertSize(LOW_RANGE_VALUE_OF_PREFIX + entityName, rangeLowValue, fieldBitwidth); + return messageBuilder.setRange( + P4RuntimeOuterClass.FieldMatch.Range.newBuilder() + .setHigh(rangeHighValue) + .setLow(rangeLowValue) + .build()) + .build(); + default: + throw new CodecException(format( + "Building of match type %s not implemented", piFieldMatch.type())); + } + } + + @Override + public PiFieldMatch decode( + P4RuntimeOuterClass.FieldMatch message, P4InfoOuterClass.Preamble tablePreamble, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException { + + String fieldMatchName = browser.matchFields(tablePreamble.getId()) + .getById(message.getFieldId()).getName(); + PiMatchFieldId headerFieldId = PiMatchFieldId.of(fieldMatchName); + + P4RuntimeOuterClass.FieldMatch.FieldMatchTypeCase typeCase = message.getFieldMatchTypeCase(); + + switch (typeCase) { + case EXACT: + P4RuntimeOuterClass.FieldMatch.Exact exactFieldMatch = message.getExact(); + ImmutableByteSequence exactValue = copyFrom(exactFieldMatch.getValue().asReadOnlyByteBuffer()); + return new PiExactFieldMatch(headerFieldId, exactValue); + case TERNARY: + P4RuntimeOuterClass.FieldMatch.Ternary ternaryFieldMatch = message.getTernary(); + ImmutableByteSequence ternaryValue = copyFrom(ternaryFieldMatch.getValue().asReadOnlyByteBuffer()); + ImmutableByteSequence ternaryMask = copyFrom(ternaryFieldMatch.getMask().asReadOnlyByteBuffer()); + return new PiTernaryFieldMatch(headerFieldId, ternaryValue, ternaryMask); + case LPM: + P4RuntimeOuterClass.FieldMatch.LPM lpmFieldMatch = message.getLpm(); + ImmutableByteSequence lpmValue = copyFrom(lpmFieldMatch.getValue().asReadOnlyByteBuffer()); + int lpmPrefixLen = lpmFieldMatch.getPrefixLen(); + return new PiLpmFieldMatch(headerFieldId, lpmValue, lpmPrefixLen); + case RANGE: + P4RuntimeOuterClass.FieldMatch.Range rangeFieldMatch = message.getRange(); + ImmutableByteSequence rangeHighValue = copyFrom(rangeFieldMatch.getHigh().asReadOnlyByteBuffer()); + ImmutableByteSequence rangeLowValue = copyFrom(rangeFieldMatch.getLow().asReadOnlyByteBuffer()); + return new PiRangeFieldMatch(headerFieldId, rangeLowValue, rangeHighValue); + default: + throw new CodecException(format( + "Decoding of field match type '%s' not implemented", typeCase.name())); + } + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/HandleCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/HandleCodec.java new file mode 100644 index 0000000000..dc617cf3e2 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/HandleCodec.java @@ -0,0 +1,119 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiActionProfileGroupHandle; +import org.onosproject.net.pi.runtime.PiActionProfileMemberHandle; +import org.onosproject.net.pi.runtime.PiCounterCellHandle; +import org.onosproject.net.pi.runtime.PiHandle; +import org.onosproject.net.pi.runtime.PiMeterCellHandle; +import org.onosproject.net.pi.runtime.PiMulticastGroupEntryHandle; +import org.onosproject.net.pi.runtime.PiTableEntryHandle; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.v1.P4RuntimeOuterClass; + +import static java.lang.String.format; +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; + +public final class HandleCodec extends AbstractCodec { + + @Override + protected P4RuntimeOuterClass.Entity encode( + PiHandle piHandle, Object ignored, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException { + final P4RuntimeOuterClass.Entity.Builder p4Entity = P4RuntimeOuterClass.Entity.newBuilder(); + + switch (piHandle.entityType()) { + case TABLE_ENTRY: + return p4Entity.setTableEntry( + CODECS.tableEntry().encodeKey( + (PiTableEntryHandle) piHandle, null, pipeconf)) + .build(); + case ACTION_PROFILE_GROUP: + return p4Entity.setActionProfileGroup( + CODECS.actionProfileGroup().encodeKey( + (PiActionProfileGroupHandle) piHandle, null, pipeconf)) + .build(); + case ACTION_PROFILE_MEMBER: + return p4Entity.setActionProfileMember( + CODECS.actionProfileMember().encodeKey( + (PiActionProfileMemberHandle) piHandle, null, pipeconf)) + .build(); + case PRE_MULTICAST_GROUP_ENTRY: + return p4Entity.setPacketReplicationEngineEntry( + P4RuntimeOuterClass.PacketReplicationEngineEntry.newBuilder() + .setMulticastGroupEntry(CODECS.multicastGroupEntry().encodeKey( + (PiMulticastGroupEntryHandle) piHandle, null, pipeconf)) + .build()) + .build(); + case METER_CELL_CONFIG: + final PiMeterCellHandle meterCellHandle = (PiMeterCellHandle) piHandle; + switch (meterCellHandle.cellId().meterType()) { + case DIRECT: + return p4Entity.setDirectMeterEntry( + CODECS.directMeterEntry().encodeKey( + meterCellHandle, null, pipeconf)) + .build(); + case INDIRECT: + return p4Entity.setMeterEntry( + CODECS.meterEntry().encodeKey( + meterCellHandle, null, pipeconf)) + .build(); + default: + throw new CodecException(format( + "Encoding of handle for %s of type %s is not supported", + piHandle.entityType(), + meterCellHandle.cellId().meterType())); + } + case COUNTER_CELL: + final PiCounterCellHandle counterCellHandle = (PiCounterCellHandle) piHandle; + switch (counterCellHandle.cellId().counterType()) { + case DIRECT: + return p4Entity.setDirectCounterEntry( + CODECS.directCounterEntry().encodeKey( + counterCellHandle, null, pipeconf)) + .build(); + case INDIRECT: + return p4Entity.setCounterEntry( + CODECS.counterEntry().encodeKey( + counterCellHandle, null, pipeconf)) + .build(); + default: + throw new CodecException(format( + "Encoding of handle for %s of type %s is not supported", + piHandle.entityType(), + counterCellHandle.cellId().counterType())); + } + case REGISTER_CELL: + case PRE_CLONE_SESSION_ENTRY: + default: + throw new CodecException(format( + "Encoding of handle for %s not supported", + piHandle.entityType())); + } + } + + @Override + protected PiHandle decode( + P4RuntimeOuterClass.Entity message, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException { + throw new CodecException("Decoding of Entity to PiHandle is not supported"); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/MeterEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/MeterEntryCodec.java new file mode 100644 index 0000000000..b2107091e4 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/MeterEntryCodec.java @@ -0,0 +1,124 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import org.onosproject.net.pi.model.PiMeterId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiMeterBand; +import org.onosproject.net.pi.runtime.PiMeterCellConfig; +import org.onosproject.net.pi.runtime.PiMeterCellHandle; +import org.onosproject.net.pi.runtime.PiMeterCellId; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.v1.P4RuntimeOuterClass; + +/** + * Codec for P4Runtime MeterEntry. + */ +public final class MeterEntryCodec + extends AbstractEntityCodec { + + static P4RuntimeOuterClass.MeterConfig getP4Config(PiMeterCellConfig piConfig) + throws CodecException { + if (piConfig.meterBands().size() != 2) { + throw new CodecException("Number of meter bands should be 2"); + } + final PiMeterBand[] bands = piConfig.meterBands().toArray(new PiMeterBand[0]); + long cir, cburst, pir, pburst; + // The band with bigger burst is peak if rate of them is equal. + if (bands[0].rate() > bands[1].rate() || + (bands[0].rate() == bands[1].rate() && + bands[0].burst() >= bands[1].burst())) { + cir = bands[1].rate(); + cburst = bands[1].burst(); + pir = bands[0].rate(); + pburst = bands[0].burst(); + } else { + cir = bands[0].rate(); + cburst = bands[0].burst(); + pir = bands[1].rate(); + pburst = bands[1].burst(); + } + return P4RuntimeOuterClass.MeterConfig.newBuilder() + .setCir(cir) + .setCburst(cburst) + .setPir(pir) + .setPburst(pburst) + .build(); + } + + @Override + protected P4RuntimeOuterClass.MeterEntry encode( + PiMeterCellConfig piEntity, Object ignored, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException, CodecException { + final int meterId = browser.meters().getByName( + piEntity.cellId().meterId().id()).getPreamble().getId(); + return P4RuntimeOuterClass.MeterEntry.newBuilder() + .setMeterId(meterId) + .setIndex(P4RuntimeOuterClass.Index.newBuilder() + .setIndex(piEntity.cellId().index()).build()) + .setConfig(getP4Config(piEntity)) + .build(); + } + + @Override + protected P4RuntimeOuterClass.MeterEntry encodeKey( + PiMeterCellHandle handle, Object metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + return keyMsgBuilder(handle.cellId(), browser).build(); + } + + @Override + protected P4RuntimeOuterClass.MeterEntry encodeKey( + PiMeterCellConfig piEntity, Object metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + return keyMsgBuilder(piEntity.cellId(), browser).build(); + } + + private P4RuntimeOuterClass.MeterEntry.Builder keyMsgBuilder( + PiMeterCellId cellId, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + final int meterId = browser.meters().getByName( + cellId.meterId().id()).getPreamble().getId(); + return P4RuntimeOuterClass.MeterEntry.newBuilder() + .setMeterId(meterId) + .setIndex(P4RuntimeOuterClass.Index.newBuilder() + .setIndex(cellId.index()).build()); + } + + @Override + protected PiMeterCellConfig decode( + P4RuntimeOuterClass.MeterEntry message, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + final String meterName = browser.meters() + .getById(message.getMeterId()) + .getPreamble() + .getName(); + return PiMeterCellConfig.builder() + .withMeterCellId(PiMeterCellId.ofIndirect( + PiMeterId.of(meterName), message.getIndex().getIndex())) + .withMeterBand(new PiMeterBand(message.getConfig().getCir(), + message.getConfig().getCburst())) + .withMeterBand(new PiMeterBand(message.getConfig().getPir(), + message.getConfig().getPburst())) + .build(); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/MulticastGroupEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/MulticastGroupEntryCodec.java new file mode 100644 index 0000000000..38080cc0c0 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/MulticastGroupEntryCodec.java @@ -0,0 +1,90 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import org.onosproject.net.PortNumber; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiMulticastGroupEntry; +import org.onosproject.net.pi.runtime.PiMulticastGroupEntryHandle; +import org.onosproject.net.pi.runtime.PiPreReplica; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.v1.P4RuntimeOuterClass; +import p4.v1.P4RuntimeOuterClass.Replica; + +import static java.lang.String.format; + +/** + * Codec for P4Runtime MulticastGroupEntry. + */ +public final class MulticastGroupEntryCodec + extends AbstractEntityCodec { + + @Override + protected P4RuntimeOuterClass.MulticastGroupEntry encode( + PiMulticastGroupEntry piEntity, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) throws CodecException { + final P4RuntimeOuterClass.MulticastGroupEntry.Builder msgBuilder = + P4RuntimeOuterClass.MulticastGroupEntry.newBuilder() + .setMulticastGroupId(piEntity.groupId()); + for (PiPreReplica replica : piEntity.replicas()) { + final int p4PortId; + try { + p4PortId = Math.toIntExact(replica.egressPort().toLong()); + } catch (ArithmeticException e) { + throw new CodecException(format( + "Cannot cast 64 bit port value '%s' to 32 bit", + replica.egressPort())); + } + msgBuilder.addReplicas( + Replica.newBuilder() + .setEgressPort(p4PortId) + .setInstance(replica.instanceId()) + .build()); + } + return msgBuilder.build(); + } + + @Override + protected P4RuntimeOuterClass.MulticastGroupEntry encodeKey( + PiMulticastGroupEntryHandle handle, Object metadata, + PiPipeconf pipeconf, P4InfoBrowser browser) { + return P4RuntimeOuterClass.MulticastGroupEntry.newBuilder() + .setMulticastGroupId(handle.groupId()).build(); + } + + @Override + protected P4RuntimeOuterClass.MulticastGroupEntry encodeKey( + PiMulticastGroupEntry piEntity, Object metadata, + PiPipeconf pipeconf, P4InfoBrowser browser) { + return P4RuntimeOuterClass.MulticastGroupEntry.newBuilder() + .setMulticastGroupId(piEntity.groupId()).build(); + } + + @Override + protected PiMulticastGroupEntry decode( + P4RuntimeOuterClass.MulticastGroupEntry message, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) { + final PiMulticastGroupEntry.Builder piEntryBuilder = PiMulticastGroupEntry.builder(); + piEntryBuilder.withGroupId(message.getMulticastGroupId()); + message.getReplicasList().stream() + .map(r -> new PiPreReplica( + PortNumber.portNumber(r.getEgressPort()), r.getInstance())) + .forEach(piEntryBuilder::addReplica); + return piEntryBuilder.build(); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4DataCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/P4DataCodec.java similarity index 94% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4DataCodec.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/P4DataCodec.java index 551bf5cc14..d55b6140c7 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4DataCodec.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/P4DataCodec.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-present Open Networking Foundation + * Copyright 2019-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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.codec; import com.google.protobuf.ByteString; import org.onlab.util.ImmutableByteSequence; @@ -41,8 +41,10 @@ import static p4.v1.P4DataOuterClass.P4HeaderUnionStack; import static p4.v1.P4DataOuterClass.P4StructLike; /** - * Encoder/decoder of PI Data entry to P4 Data entry protobuf - * messages, and vice versa. + * Encoder/decoder of PI Data entry to P4 Data entry protobuf messages, and vice + * versa. + *

+ * TODO: implement codec for each P4Data type using AbstractP4RuntimeCodec. */ final class P4DataCodec { @@ -145,7 +147,7 @@ final class P4DataCodec { builder.setHeader(encodeHeader((PiHeader) piData)); break; case HEADERSTACK: - P4HeaderStack.Builder headerStack = P4HeaderStack.newBuilder(); + P4HeaderStack.Builder headerStack = P4HeaderStack.newBuilder(); int i = 0; for (PiHeader header : ((PiHeaderStack) piData).headers()) { headerStack.setEntries(i, encodeHeader(header)); @@ -157,7 +159,7 @@ final class P4DataCodec { builder.setHeaderUnion(encodeHeaderUnion((PiHeaderUnion) piData)); break; case HEADERUNIONSTACK: - P4HeaderUnionStack.Builder headerUnionStack = P4HeaderUnionStack.newBuilder(); + P4HeaderUnionStack.Builder headerUnionStack = P4HeaderUnionStack.newBuilder(); int j = 0; for (PiHeaderUnion headerUnion : ((PiHeaderUnionStack) piData).headerUnions()) { headerUnionStack.setEntries(j, encodeHeaderUnion(headerUnion)); diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketInCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketInCodec.java new file mode 100644 index 0000000000..e4de89615c --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketInCodec.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import org.onosproject.net.pi.model.PiPacketOperationType; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiPacketOperation; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.config.v1.P4InfoOuterClass; +import p4.v1.P4RuntimeOuterClass; + +import static org.onlab.util.ImmutableByteSequence.copyFrom; +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; + +/** + * Codec for P4Runtime PacketIn. Only decoding is supported, as encoding is not + * meaningful in this case (packet-ins are always generated by the server). + */ +public final class PacketInCodec + extends AbstractCodec { + + private static final String PACKET_IN = "packet_in"; + + @Override + protected P4RuntimeOuterClass.PacketIn encode( + PiPacketOperation piEntity, Object ignored, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException { + throw new CodecException("Encoding of packet-in is not supported"); + } + + @Override + protected PiPacketOperation decode( + P4RuntimeOuterClass.PacketIn message, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException { + final P4InfoOuterClass.Preamble ctrlPktMetaPreamble = browser + .controllerPacketMetadatas() + .getByName(PACKET_IN) + .getPreamble(); + return PiPacketOperation.builder() + .withType(PiPacketOperationType.PACKET_IN) + .withMetadatas(CODECS.packetMetadata().decodeAll( + message.getMetadataList(), ctrlPktMetaPreamble, pipeconf)) + .withData(copyFrom(message.getPayload().asReadOnlyByteBuffer())) + .build(); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketMetadataCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketMetadataCodec.java new file mode 100644 index 0000000000..3f78085fdb --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketMetadataCodec.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import com.google.protobuf.ByteString; +import org.onosproject.net.pi.model.PiPacketMetadataId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiPacketMetadata; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.config.v1.P4InfoOuterClass; +import p4.v1.P4RuntimeOuterClass; + +import static org.onlab.util.ImmutableByteSequence.copyFrom; + +/** + * Coded for P4Runtime PacketMetadata. The metadata is expected to be a Preamble + * of a P4Info.ControllerPacketMetadata message. + */ +public final class PacketMetadataCodec + extends AbstractCodec { + + @Override + protected P4RuntimeOuterClass.PacketMetadata encode( + PiPacketMetadata piEntity, P4InfoOuterClass.Preamble ctrlPktMetaPreamble, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + final int metadataId = browser + .packetMetadatas(ctrlPktMetaPreamble.getId()) + .getByName(piEntity.id().id()).getId(); + return P4RuntimeOuterClass.PacketMetadata.newBuilder() + .setMetadataId(metadataId) + .setValue(ByteString.copyFrom(piEntity.value().asReadOnlyBuffer())) + .build(); + } + + @Override + protected PiPacketMetadata decode( + P4RuntimeOuterClass.PacketMetadata message, + P4InfoOuterClass.Preamble ctrlPktMetaPreamble, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException { + final String packetMetadataName = browser + .packetMetadatas(ctrlPktMetaPreamble.getId()) + .getById(message.getMetadataId()).getName(); + final PiPacketMetadataId metadataId = PiPacketMetadataId + .of(packetMetadataName); + return PiPacketMetadata.builder() + .withId(metadataId) + .withValue(copyFrom(message.getValue().asReadOnlyByteBuffer())) + .build(); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketOutCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketOutCodec.java new file mode 100644 index 0000000000..6d020c3fb9 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/PacketOutCodec.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import com.google.protobuf.ByteString; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.runtime.PiPacketOperation; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.config.v1.P4InfoOuterClass; +import p4.v1.P4RuntimeOuterClass; + +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; + +/** + * Codec for P4Runtime PacketOut. Only encoding is supported, as decoding is not + * meaningful in this case (packet-outs are always generated by the client). + */ +public final class PacketOutCodec + extends AbstractCodec { + + private static final String PACKET_OUT = "packet_out"; + + @Override + protected P4RuntimeOuterClass.PacketOut encode( + PiPacketOperation piPacket, Object ignored, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException { + final P4InfoOuterClass.Preamble ctrlPktMetaPreamble = browser + .controllerPacketMetadatas() + .getByName(PACKET_OUT) + .getPreamble(); + return P4RuntimeOuterClass.PacketOut.newBuilder() + .addAllMetadata(CODECS.packetMetadata().encodeAll( + piPacket.metadatas(), ctrlPktMetaPreamble, pipeconf)) + .setPayload(ByteString.copyFrom(piPacket.data().asReadOnlyBuffer())) + .build(); + + } + + @Override + protected PiPacketOperation decode( + P4RuntimeOuterClass.PacketOut message, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException { + throw new CodecException("Decoding of packet-out is not supported"); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/TableEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/TableEntryCodec.java new file mode 100644 index 0000000000..f3f309bc97 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/TableEntryCodec.java @@ -0,0 +1,222 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl.codec; + +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.model.PiTableId; +import org.onosproject.net.pi.runtime.PiAction; +import org.onosproject.net.pi.runtime.PiActionProfileGroupId; +import org.onosproject.net.pi.runtime.PiActionProfileMemberId; +import org.onosproject.net.pi.runtime.PiCounterCellData; +import org.onosproject.net.pi.runtime.PiMatchKey; +import org.onosproject.net.pi.runtime.PiTableAction; +import org.onosproject.net.pi.runtime.PiTableEntry; +import org.onosproject.net.pi.runtime.PiTableEntryHandle; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import p4.config.v1.P4InfoOuterClass; +import p4.v1.P4RuntimeOuterClass; + +import java.util.OptionalInt; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; +import static org.onosproject.p4runtime.ctl.codec.Codecs.CODECS; + +/** + * Codec for P4Runtime TableEntry. + */ +public final class TableEntryCodec + extends AbstractEntityCodec { + + @Override + protected P4RuntimeOuterClass.TableEntry encode( + PiTableEntry piTableEntry, Object ignored, PiPipeconf pipeconf, + P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException { + final P4RuntimeOuterClass.TableEntry.Builder tableEntryMsgBuilder = + keyMsgBuilder(piTableEntry.table(), piTableEntry.matchKey(), + piTableEntry.priority(), pipeconf, browser); + // Controller metadata (cookie) + tableEntryMsgBuilder.setControllerMetadata(piTableEntry.cookie()); + // Timeout. + if (piTableEntry.timeout().isPresent()) { + // FIXME: timeout is supported in P4Runtime v1.0 + log.warn("Found PI table entry with timeout set, " + + "not supported in P4Runtime: {}", piTableEntry); + } + // Table action. + if (piTableEntry.action() != null) { + tableEntryMsgBuilder.setAction( + encodePiTableAction(piTableEntry.action(), pipeconf)); + } + // Counter. + if (piTableEntry.counter() != null) { + tableEntryMsgBuilder.setCounterData(encodeCounter(piTableEntry.counter())); + } + return tableEntryMsgBuilder.build(); + } + + @Override + protected P4RuntimeOuterClass.TableEntry encodeKey( + PiTableEntryHandle handle, Object metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) throws CodecException, P4InfoBrowser.NotFoundException { + return keyMsgBuilder(handle.tableId(), handle.matchKey(), + handle.priority(), pipeconf, browser).build(); + } + + @Override + protected P4RuntimeOuterClass.TableEntry encodeKey( + PiTableEntry piEntity, Object metadata, PiPipeconf pipeconf, + P4InfoBrowser browser) throws CodecException, P4InfoBrowser.NotFoundException { + return keyMsgBuilder(piEntity.table(), piEntity.matchKey(), + piEntity.priority(), pipeconf, browser).build(); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private P4RuntimeOuterClass.TableEntry.Builder keyMsgBuilder( + PiTableId tableId, PiMatchKey matchKey, OptionalInt priority, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws P4InfoBrowser.NotFoundException, CodecException { + final P4RuntimeOuterClass.TableEntry.Builder tableEntryMsgBuilder = + P4RuntimeOuterClass.TableEntry.newBuilder(); + final P4InfoOuterClass.Preamble tablePreamble = browser.tables() + .getByName(tableId.id()).getPreamble(); + // Table id. + tableEntryMsgBuilder.setTableId(tablePreamble.getId()); + // Field matches. + if (matchKey.equals(PiMatchKey.EMPTY)) { + tableEntryMsgBuilder.setIsDefaultAction(true); + } else { + tableEntryMsgBuilder.addAllMatch( + CODECS.fieldMatch().encodeAll( + matchKey.fieldMatches(), + tablePreamble, pipeconf)); + } + // Priority. + priority.ifPresent(tableEntryMsgBuilder::setPriority); + return tableEntryMsgBuilder; + } + + @Override + protected PiTableEntry decode( + P4RuntimeOuterClass.TableEntry message, Object ignored, + PiPipeconf pipeconf, P4InfoBrowser browser) + throws CodecException, P4InfoBrowser.NotFoundException { + PiTableEntry.Builder piTableEntryBuilder = PiTableEntry.builder(); + + P4InfoOuterClass.Preamble tablePreamble = browser.tables() + .getById(message.getTableId()).getPreamble(); + + // Table id. + piTableEntryBuilder.forTable(PiTableId.of(tablePreamble.getName())); + + // Priority. + if (message.getPriority() > 0) { + piTableEntryBuilder.withPriority(message.getPriority()); + } + + // Controller metadata (cookie) + piTableEntryBuilder.withCookie(message.getControllerMetadata()); + + // Table action. + if (message.hasAction()) { + piTableEntryBuilder.withAction(decodeTableActionMsg( + message.getAction(), pipeconf)); + } + + // Timeout. + // FIXME: how to decode table entry messages with timeout, given that + // the timeout value is lost after encoding? + + // Match key for field matches. + piTableEntryBuilder.withMatchKey( + PiMatchKey.builder() + .addFieldMatches(CODECS.fieldMatch().decodeAll( + message.getMatchList(), + tablePreamble, pipeconf)) + .build()); + + // Counter. + piTableEntryBuilder.withCounterCellData(decodeCounter(message.getCounterData())); + + return piTableEntryBuilder.build(); + } + + private P4RuntimeOuterClass.TableAction encodePiTableAction( + PiTableAction piTableAction, PiPipeconf pipeconf) + throws CodecException { + checkNotNull(piTableAction, "Cannot encode null PiTableAction"); + final P4RuntimeOuterClass.TableAction.Builder tableActionMsgBuilder = + P4RuntimeOuterClass.TableAction.newBuilder(); + switch (piTableAction.type()) { + case ACTION: + P4RuntimeOuterClass.Action theAction = CODECS.action() + .encode((PiAction) piTableAction, null, pipeconf); + tableActionMsgBuilder.setAction(theAction); + break; + case ACTION_PROFILE_GROUP_ID: + tableActionMsgBuilder.setActionProfileGroupId( + ((PiActionProfileGroupId) piTableAction).id()); + break; + case ACTION_PROFILE_MEMBER_ID: + tableActionMsgBuilder.setActionProfileMemberId( + ((PiActionProfileMemberId) piTableAction).id()); + break; + default: + throw new CodecException( + format("Building of table action type %s not implemented", + piTableAction.type())); + } + return tableActionMsgBuilder.build(); + } + + private PiTableAction decodeTableActionMsg( + P4RuntimeOuterClass.TableAction tableActionMsg, PiPipeconf pipeconf) + throws CodecException { + P4RuntimeOuterClass.TableAction.TypeCase typeCase = tableActionMsg.getTypeCase(); + switch (typeCase) { + case ACTION: + P4RuntimeOuterClass.Action actionMsg = tableActionMsg.getAction(); + return CODECS.action().decode( + actionMsg, null, pipeconf); + case ACTION_PROFILE_GROUP_ID: + return PiActionProfileGroupId.of( + tableActionMsg.getActionProfileGroupId()); + case ACTION_PROFILE_MEMBER_ID: + return PiActionProfileMemberId.of( + tableActionMsg.getActionProfileMemberId()); + default: + throw new CodecException( + format("Decoding of table action type %s not implemented", + typeCase.name())); + } + } + + private P4RuntimeOuterClass.CounterData encodeCounter( + PiCounterCellData piCounterCellData) { + return P4RuntimeOuterClass.CounterData.newBuilder() + .setPacketCount(piCounterCellData.packets()) + .setByteCount(piCounterCellData.bytes()).build(); + } + + private PiCounterCellData decodeCounter( + P4RuntimeOuterClass.CounterData counterData) { + return new PiCounterCellData( + counterData.getPacketCount(), counterData.getByteCount()); + } +} diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeUtils.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/Utils.java similarity index 80% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeUtils.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/Utils.java index 604b1f01e2..c72489a7b3 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeUtils.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-present Open Networking Foundation + * Copyright 2019-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. @@ -14,19 +14,18 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.codec; import com.google.protobuf.ByteString; -import p4.v1.P4RuntimeOuterClass; import static java.lang.String.format; /** - * Utilities for P4 runtime control. + * Codec utilities. */ -final class P4RuntimeUtils { +final class Utils { - private P4RuntimeUtils() { + private Utils() { // Hide default construction } @@ -50,8 +49,4 @@ final class P4RuntimeUtils { entityDescr, bitWidth, prefixLength)); } } - - static P4RuntimeOuterClass.Index indexMsg(long index) { - return P4RuntimeOuterClass.Index.newBuilder().setIndex(index).build(); - } } diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/package-info.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/package-info.java new file mode 100644 index 0000000000..25cee2b61c --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/codec/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2019-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. + */ + +/** + * Classes to translates from PI framework-related objects to P4Runtime protobuf + * messages, and vice versa. + */ +package org.onosproject.p4runtime.ctl.codec; diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ArbitrationResponse.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/ArbitrationUpdateEvent.java similarity index 84% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ArbitrationResponse.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/ArbitrationUpdateEvent.java index f93a4cdf42..80e3e9875b 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ArbitrationResponse.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/ArbitrationUpdateEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-present Open Networking Foundation + * Copyright 2019-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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.controller; import org.onosproject.net.DeviceId; import org.onosproject.p4runtime.api.P4RuntimeEventSubject; @@ -22,7 +22,7 @@ import org.onosproject.p4runtime.api.P4RuntimeEventSubject; /** * Default implementation of arbitration in P4Runtime. */ -final class ArbitrationResponse implements P4RuntimeEventSubject { +public final class ArbitrationUpdateEvent implements P4RuntimeEventSubject { private DeviceId deviceId; private boolean isMaster; @@ -33,7 +33,7 @@ final class ArbitrationResponse implements P4RuntimeEventSubject { * @param deviceId the device * @param isMaster true if arbitration response signals master status */ - ArbitrationResponse(DeviceId deviceId, boolean isMaster) { + public ArbitrationUpdateEvent(DeviceId deviceId, boolean isMaster) { this.deviceId = deviceId; this.isMaster = isMaster; } diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/BaseP4RuntimeEventSubject.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/BaseEventSubject.java similarity index 81% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/BaseP4RuntimeEventSubject.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/BaseEventSubject.java index a78f10cab2..089d934522 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/BaseP4RuntimeEventSubject.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/BaseEventSubject.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-present Open Networking Foundation + * Copyright 2019-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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.controller; import org.onosproject.net.DeviceId; import org.onosproject.p4runtime.api.P4RuntimeEventSubject; @@ -23,7 +23,7 @@ import org.onosproject.p4runtime.api.P4RuntimeEventSubject; * Base P4Runtime event subject that carries just the device ID that originated * the event. */ -final class BaseP4RuntimeEventSubject implements P4RuntimeEventSubject { +public final class BaseEventSubject implements P4RuntimeEventSubject { private DeviceId deviceId; @@ -32,7 +32,7 @@ final class BaseP4RuntimeEventSubject implements P4RuntimeEventSubject { * * @param deviceId the device */ - BaseP4RuntimeEventSubject(DeviceId deviceId) { + public BaseEventSubject(DeviceId deviceId) { this.deviceId = deviceId; } diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ChannelEvent.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/ChannelEvent.java similarity index 83% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ChannelEvent.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/ChannelEvent.java index 6e33514fa1..0a77e46a68 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ChannelEvent.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/ChannelEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-present Open Networking Foundation + * Copyright 2019-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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.controller; import org.onosproject.net.DeviceId; import org.onosproject.p4runtime.api.P4RuntimeEventSubject; @@ -22,9 +22,9 @@ import org.onosproject.p4runtime.api.P4RuntimeEventSubject; /** * Channel event in P4Runtime. */ -final class ChannelEvent implements P4RuntimeEventSubject { +public final class ChannelEvent implements P4RuntimeEventSubject { - enum Type { + public enum Type { OPEN, CLOSED, ERROR @@ -39,7 +39,7 @@ final class ChannelEvent implements P4RuntimeEventSubject { * @param deviceId the device * @param type error type */ - ChannelEvent(DeviceId deviceId, Type type) { + public ChannelEvent(DeviceId deviceId, Type type) { this.deviceId = deviceId; this.type = type; } diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/DistributedElectionIdGenerator.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/DistributedElectionIdGenerator.java similarity index 94% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/DistributedElectionIdGenerator.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/DistributedElectionIdGenerator.java index 75c068eb7a..980ab1144a 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/DistributedElectionIdGenerator.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/DistributedElectionIdGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-present Open Networking Foundation + * Copyright 2019-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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.controller; import org.onlab.util.KryoNamespace; import org.onosproject.net.DeviceId; @@ -34,7 +34,7 @@ import static org.slf4j.LoggerFactory.getLogger; /** * Distributed implementation of a generator of P4Runtime election IDs. */ -class DistributedElectionIdGenerator { +final class DistributedElectionIdGenerator { private final Logger log = getLogger(this.getClass()); diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeControllerImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/P4RuntimeControllerImpl.java similarity index 90% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeControllerImpl.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/P4RuntimeControllerImpl.java index ac1d90fafe..affbf7d741 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeControllerImpl.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/P4RuntimeControllerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-present Open Networking Foundation + * Copyright 2019-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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.controller; import com.google.common.collect.Maps; import io.grpc.ManagedChannel; @@ -22,12 +22,14 @@ import org.onosproject.grpc.ctl.AbstractGrpcClientController; import org.onosproject.net.DeviceId; import org.onosproject.net.device.DeviceAgentEvent; import org.onosproject.net.device.DeviceAgentListener; +import org.onosproject.net.pi.service.PiPipeconfService; import org.onosproject.net.provider.ProviderId; import org.onosproject.p4runtime.api.P4RuntimeClient; import org.onosproject.p4runtime.api.P4RuntimeClientKey; import org.onosproject.p4runtime.api.P4RuntimeController; import org.onosproject.p4runtime.api.P4RuntimeEvent; import org.onosproject.p4runtime.api.P4RuntimeEventListener; +import org.onosproject.p4runtime.ctl.client.P4RuntimeClientImpl; import org.onosproject.store.service.StorageService; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -61,6 +63,9 @@ public class P4RuntimeControllerImpl @Reference(cardinality = ReferenceCardinality.MANDATORY) private StorageService storageService; + @Reference(cardinality = ReferenceCardinality.MANDATORY) + private PiPipeconfService pipeconfService; + @Activate public void activate() { super.activate(); @@ -80,7 +85,7 @@ public class P4RuntimeControllerImpl @Override protected P4RuntimeClient createClientInstance(P4RuntimeClientKey clientKey, ManagedChannel channel) { - return new P4RuntimeClientImpl(clientKey, channel, this); + return new P4RuntimeClientImpl(clientKey, channel, this, pipeconfService); } @Override @@ -102,11 +107,11 @@ public class P4RuntimeControllerImpl }); } - BigInteger newMasterElectionId(DeviceId deviceId) { + public BigInteger newMasterElectionId(DeviceId deviceId) { return electionIdGenerator.generate(deviceId); } - void postEvent(P4RuntimeEvent event) { + public void postEvent(P4RuntimeEvent event) { switch (event.type()) { case CHANNEL_EVENT: handleChannelEvent(event); @@ -153,7 +158,7 @@ public class P4RuntimeControllerImpl private void handleArbitrationReply(P4RuntimeEvent event) { final DeviceId deviceId = event.subject().deviceId(); - final ArbitrationResponse response = (ArbitrationResponse) event.subject(); + final ArbitrationUpdateEvent response = (ArbitrationUpdateEvent) event.subject(); final DeviceAgentEvent.Type roleType = response.isMaster() ? DeviceAgentEvent.Type.ROLE_MASTER : DeviceAgentEvent.Type.ROLE_STANDBY; diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketInEvent.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/PacketInEvent.java similarity index 89% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketInEvent.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/PacketInEvent.java index dddee2b045..4a983a87ff 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketInEvent.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/PacketInEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-present Open Networking Foundation + * Copyright 2019-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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.controller; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; @@ -27,12 +27,12 @@ import static com.google.common.base.Preconditions.checkNotNull; /** * P4Runtime packet-in. */ -final class PacketInEvent implements P4RuntimePacketIn { +public final class PacketInEvent implements P4RuntimePacketIn { private final DeviceId deviceId; private final PiPacketOperation operation; - PacketInEvent(DeviceId deviceId, PiPacketOperation operation) { + public PacketInEvent(DeviceId deviceId, PiPacketOperation operation) { this.deviceId = checkNotNull(deviceId); this.operation = checkNotNull(operation); } diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/package-info.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/package-info.java new file mode 100644 index 0000000000..4d37da983b --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/controller/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2019-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. + */ + +/** + * P4Runtime controller implementation classes. + */ +package org.onosproject.p4runtime.ctl.controller; diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/utils/P4InfoBrowser.java similarity index 87% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/utils/P4InfoBrowser.java index 801ce369a1..29cddffb91 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/utils/P4InfoBrowser.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-present Open Networking Foundation + * Copyright 2019-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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.utils; import com.google.common.collect.Maps; @@ -40,7 +40,7 @@ import static java.lang.String.format; /** * Utility class to easily retrieve information from a P4Info protobuf message. */ -final class P4InfoBrowser { +public final class P4InfoBrowser { private final EntityBrowser tables = new EntityBrowser<>("table"); private final EntityBrowser actions = new EntityBrowser<>("action"); @@ -61,7 +61,7 @@ final class P4InfoBrowser { * * @param p4info P4Info protobuf message */ - P4InfoBrowser(P4Info p4info) { + public P4InfoBrowser(P4Info p4info) { parseP4Info(p4info); } @@ -123,7 +123,7 @@ final class P4InfoBrowser { * * @return table browser */ - EntityBrowser
tables() { + public EntityBrowser
tables() { return tables; } @@ -132,7 +132,7 @@ final class P4InfoBrowser { * * @return action browser */ - EntityBrowser actions() { + public EntityBrowser actions() { return actions; } @@ -141,7 +141,7 @@ final class P4InfoBrowser { * * @return action profile browser */ - EntityBrowser actionProfiles() { + public EntityBrowser actionProfiles() { return actionProfiles; } @@ -150,7 +150,7 @@ final class P4InfoBrowser { * * @return counter browser */ - EntityBrowser counters() { + public EntityBrowser counters() { return counters; } @@ -159,7 +159,7 @@ final class P4InfoBrowser { * * @return direct counter browser */ - EntityBrowser directCounters() { + public EntityBrowser directCounters() { return directCounters; } @@ -168,7 +168,7 @@ final class P4InfoBrowser { * * @return meter browser */ - EntityBrowser meters() { + public EntityBrowser meters() { return meters; } @@ -177,7 +177,7 @@ final class P4InfoBrowser { * * @return table browser */ - EntityBrowser directMeters() { + public EntityBrowser directMeters() { return directMeters; } @@ -186,7 +186,7 @@ final class P4InfoBrowser { * * @return controller packet metadata browser */ - EntityBrowser controllerPacketMetadatas() { + public EntityBrowser controllerPacketMetadatas() { return ctrlPktMetadatas; } @@ -197,7 +197,7 @@ final class P4InfoBrowser { * @return action params browser * @throws NotFoundException if the action cannot be found */ - EntityBrowser actionParams(int actionId) throws NotFoundException { + public EntityBrowser actionParams(int actionId) throws NotFoundException { // Throws exception if action id is not found. actions.getById(actionId); return actionParams.get(actionId); @@ -210,7 +210,7 @@ final class P4InfoBrowser { * @return match field browser * @throws NotFoundException if the table cannot be found */ - EntityBrowser matchFields(int tableId) throws NotFoundException { + public EntityBrowser matchFields(int tableId) throws NotFoundException { // Throws exception if action id is not found. tables.getById(tableId); return matchFields.get(tableId); @@ -223,7 +223,7 @@ final class P4InfoBrowser { * @return metadata browser * @throws NotFoundException controller packet metadata cannot be found */ - EntityBrowser packetMetadatas(int controllerPacketMetadataId) + public EntityBrowser packetMetadatas(int controllerPacketMetadataId) throws NotFoundException { // Throws exception if controller packet metadata id is not found. ctrlPktMetadatas.getById(controllerPacketMetadataId); @@ -235,7 +235,7 @@ final class P4InfoBrowser { * * @param protobuf message type */ - static final class EntityBrowser { + public static final class EntityBrowser { private String entityName; private final Map names = Maps.newHashMap(); @@ -254,7 +254,7 @@ final class P4InfoBrowser { * @param id entity id * @param entity entity message */ - void add(String name, String alias, int id, T entity) { + private void add(String name, String alias, int id, T entity) { checkNotNull(name); checkArgument(!name.isEmpty(), "Name cannot be empty"); checkNotNull(entity); @@ -271,7 +271,7 @@ final class P4InfoBrowser { * @param preamble P4Info preamble protobuf message * @param entity entity message */ - void addWithPreamble(Preamble preamble, T entity) { + private void addWithPreamble(Preamble preamble, T entity) { checkNotNull(preamble); add(preamble.getName(), preamble.getAlias(), preamble.getId(), entity); } @@ -282,7 +282,7 @@ final class P4InfoBrowser { * @param name entity name * @return boolean */ - boolean hasName(String name) { + public boolean hasName(String name) { return names.containsKey(name); } @@ -293,7 +293,7 @@ final class P4InfoBrowser { * @return entity message * @throws NotFoundException if the entity cannot be found */ - T getByName(String name) throws NotFoundException { + public T getByName(String name) throws NotFoundException { if (hasName(name)) { return names.get(name); } else { @@ -311,7 +311,7 @@ final class P4InfoBrowser { * @param id entity id * @return boolean */ - boolean hasId(int id) { + public boolean hasId(int id) { return ids.containsKey(id); } @@ -322,7 +322,7 @@ final class P4InfoBrowser { * @return entity message * @throws NotFoundException if the entity cannot be found */ - T getById(int id) throws NotFoundException { + public T getById(int id) throws NotFoundException { if (!hasId(id)) { throw new NotFoundException(entityName, id); } @@ -335,12 +335,13 @@ final class P4InfoBrowser { */ public static final class NotFoundException extends Exception { - NotFoundException(String entityName, String key, String hint) { + public NotFoundException(String entityName, String key, String hint) { super(format( - "No such %s in P4Info with name '%s'%s", entityName, key, hint.isEmpty() ? "" : " (" + hint + ")")); + "No such %s in P4Info with name '%s'%s", + entityName, key, hint.isEmpty() ? "" : " (" + hint + ")")); } - NotFoundException(String entityName, int id) { + public NotFoundException(String entityName, int id) { super(format("No such %s in P4Info with id '%d'", entityName, id)); } } diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PipeconfHelper.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/utils/PipeconfHelper.java similarity index 91% rename from protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PipeconfHelper.java rename to protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/utils/PipeconfHelper.java index 85fa2a89ba..dec74387a3 100644 --- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PipeconfHelper.java +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/utils/PipeconfHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-present Open Networking Foundation + * Copyright 2019-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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.utils; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -37,9 +37,9 @@ import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT import static org.slf4j.LoggerFactory.getLogger; /** - * Utility class to deal with pipeconfs in the context of P4runtime. + * Utility class to deal with pipeconfs in the context of P4Runtime. */ -final class PipeconfHelper { +public final class PipeconfHelper { private static final int P4INFO_BROWSER_EXPIRE_TIME_IN_MIN = 10; private static final Logger log = getLogger(PipeconfHelper.class); @@ -60,7 +60,7 @@ final class PipeconfHelper { * @param pipeconf pipeconf * @return P4Info or null */ - static P4Info getP4Info(PiPipeconf pipeconf) { + public static P4Info getP4Info(PiPipeconf pipeconf) { return P4INFOS.computeIfAbsent(pipeconf.id(), piPipeconfId -> { if (!pipeconf.extension(P4_INFO_TEXT).isPresent()) { log.warn("Missing P4Info extension in pipeconf {}", pipeconf.id()); @@ -88,7 +88,7 @@ final class PipeconfHelper { * @param pipeconf pipeconf * @return P4Info browser or null */ - static P4InfoBrowser getP4InfoBrowser(PiPipeconf pipeconf) { + public static P4InfoBrowser getP4InfoBrowser(PiPipeconf pipeconf) { try { return BROWSERS.get(pipeconf.id(), () -> { P4Info p4info = PipeconfHelper.getP4Info(pipeconf); diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/utils/package-info.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/utils/package-info.java new file mode 100644 index 0000000000..ae09455394 --- /dev/null +++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/utils/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2019-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. + */ + +/** + * Utility classes for the P4Runtime protocol subsystem. + */ +package org.onosproject.p4runtime.ctl.utils; diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/MockP4RuntimeServer.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/MockP4RuntimeServer.java index 99b60c48cf..e956fd7116 100644 --- a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/MockP4RuntimeServer.java +++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/MockP4RuntimeServer.java @@ -79,6 +79,8 @@ public class MockP4RuntimeServer extends P4RuntimeGrpc.P4RuntimeImplBase { @Override public void write(WriteRequest request, StreamObserver responseObserver) { writeReqs.add(request); + responseObserver.onNext(WriteResponse.getDefaultInstance()); + responseObserver.onCompleted(); complete(); } diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/MockPipeconfService.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/MockPipeconfService.java new file mode 100644 index 0000000000..0569761f30 --- /dev/null +++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/MockPipeconfService.java @@ -0,0 +1,66 @@ +/* + * Copyright 2019-present Open Networking Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.p4runtime.ctl; + +import org.onosproject.net.DeviceId; +import org.onosproject.net.pi.model.PiPipeconf; +import org.onosproject.net.pi.model.PiPipeconfId; +import org.onosproject.net.pi.service.PiPipeconfService; + +import java.util.Optional; + +public class MockPipeconfService implements PiPipeconfService { + @Override + public void register(PiPipeconf pipeconf) throws IllegalStateException { + + } + + @Override + public void remove(PiPipeconfId pipeconfId) throws IllegalStateException { + + } + + @Override + public Iterable getPipeconfs() { + return null; + } + + @Override + public Optional getPipeconf(PiPipeconfId id) { + return Optional.empty(); + } + + @Override + public Optional getPipeconf(DeviceId deviceId) { + return Optional.empty(); + } + + @Override + public void bindToDevice(PiPipeconfId pipeconfId, DeviceId deviceId) { + + } + + @Override + public String getMergedDriver(DeviceId deviceId, PiPipeconfId pipeconfId) { + return null; + } + + @Override + public Optional ofDevice(DeviceId deviceId) { + return Optional.empty(); + } +} 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 a7f7183fd9..c6a43cb8a2 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 @@ -45,6 +45,8 @@ import org.onosproject.net.pi.runtime.PiActionProfileGroupId; import org.onosproject.net.pi.runtime.PiActionProfileMember; import org.onosproject.net.pi.runtime.PiActionProfileMemberId; import org.onosproject.p4runtime.api.P4RuntimeClientKey; +import org.onosproject.p4runtime.ctl.client.P4RuntimeClientImpl; +import org.onosproject.p4runtime.ctl.controller.P4RuntimeControllerImpl; import p4.v1.P4RuntimeOuterClass.ActionProfileGroup; import p4.v1.P4RuntimeOuterClass.ActionProfileMember; import p4.v1.P4RuntimeOuterClass.Entity; @@ -65,7 +67,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT; -import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT; import static p4.v1.P4RuntimeOuterClass.Action; import static p4.v1.P4RuntimeOuterClass.ReadResponse; @@ -108,7 +109,7 @@ public class P4RuntimeGroupTest { private static final String P4R_IP = "127.0.0.1"; private static final int P4R_PORT = 50010; - private P4RuntimeClientImpl client; + private org.onosproject.p4runtime.ctl.client.P4RuntimeClientImpl client; private P4RuntimeControllerImpl controller; private static MockP4RuntimeServer p4RuntimeServerImpl = new MockP4RuntimeServer(); private static Server grpcServer; @@ -157,16 +158,16 @@ public class P4RuntimeGroupTest { @Before public void setup() { - controller = niceMock(P4RuntimeControllerImpl.class); + controller = niceMock(org.onosproject.p4runtime.ctl.controller.P4RuntimeControllerImpl.class); P4RuntimeClientKey clientKey = new P4RuntimeClientKey(DEVICE_ID, P4R_IP, P4R_PORT, P4_DEVICE_ID); - client = new P4RuntimeClientImpl(clientKey, grpcChannel, controller); - client.becomeMaster(); + client = new P4RuntimeClientImpl(clientKey, grpcChannel, controller, new MockPipeconfService()); } @Test public void testInsertPiActionProfileGroup() throws Exception { CompletableFuture complete = p4RuntimeServerImpl.expectRequests(1); - client.writeActionProfileGroup(GROUP, INSERT, PIPECONF); + client.write(PIPECONF).insert(GROUP).submitSync(); + assertTrue(client.write(PIPECONF).insert(GROUP).submitSync().isSuccess()); complete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS); WriteRequest result = p4RuntimeServerImpl.getWriteReqs().get(0); assertEquals(1, result.getDeviceId()); @@ -195,7 +196,8 @@ public class P4RuntimeGroupTest { @Test public void testInsertPiActionMembers() throws Exception { CompletableFuture complete = p4RuntimeServerImpl.expectRequests(1); - client.writeActionProfileMembers(GROUP_MEMBER_INSTANCES, INSERT, PIPECONF); + assertTrue(client.write(PIPECONF).insert(GROUP_MEMBER_INSTANCES) + .submitSync().isSuccess()); complete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS); WriteRequest result = p4RuntimeServerImpl.getWriteReqs().get(0); assertEquals(1, result.getDeviceId()); @@ -243,11 +245,10 @@ public class P4RuntimeGroupTest { p4RuntimeServerImpl.willReturnReadResult(responses); CompletableFuture complete = p4RuntimeServerImpl.expectRequests(1); - CompletableFuture> groupsComplete = client.dumpActionProfileGroups( - ACT_PROF_ID, PIPECONF); + Collection groups = client.read(PIPECONF) + .actionProfileGroups(ACT_PROF_ID) + .submitSync().all(PiActionProfileGroup.class); complete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS); - - Collection groups = groupsComplete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS); assertEquals(1, groups.size()); PiActionProfileGroup piActionGroup = groups.iterator().next(); assertEquals(ACT_PROF_ID, piActionGroup.actionProfile()); @@ -293,11 +294,10 @@ public class P4RuntimeGroupTest { p4RuntimeServerImpl.willReturnReadResult(responses); CompletableFuture complete = p4RuntimeServerImpl.expectRequests(1); - CompletableFuture> membersComplete = client.dumpActionProfileMembers( - ACT_PROF_ID, PIPECONF); + Collection piMembers = client.read(PIPECONF) + .actionProfileMembers(ACT_PROF_ID).submitSync() + .all(PiActionProfileMember.class); complete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS); - - Collection piMembers = membersComplete.get(DEFAULT_TIMEOUT_TIME, TimeUnit.SECONDS); assertEquals(3, piMembers.size()); assertTrue(GROUP_MEMBER_INSTANCES.containsAll(piMembers)); assertTrue(piMembers.containsAll(GROUP_MEMBER_INSTANCES)); diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/PacketInEventTest.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/PacketInEventTest.java index 0a1f82ecc8..b73f4b5a00 100644 --- a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/PacketInEventTest.java +++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/PacketInEventTest.java @@ -21,9 +21,10 @@ import org.junit.Before; import org.junit.Test; import org.onlab.util.ImmutableByteSequence; import org.onosproject.net.DeviceId; -import org.onosproject.net.pi.model.PiControlMetadataId; -import org.onosproject.net.pi.runtime.PiControlMetadata; +import org.onosproject.net.pi.model.PiPacketMetadataId; +import org.onosproject.net.pi.runtime.PiPacketMetadata; import org.onosproject.net.pi.runtime.PiPacketOperation; +import org.onosproject.p4runtime.ctl.controller.PacketInEvent; import static org.onlab.util.ImmutableByteSequence.copyFrom; import static org.onosproject.net.pi.model.PiPacketOperationType.PACKET_IN; @@ -46,9 +47,9 @@ public class PacketInEventTest { private PiPacketOperation packetOperation2; private PiPacketOperation nullPacketOperation = null; - private PacketInEvent packetIn; - private PacketInEvent sameAsPacketIn; - private PacketInEvent packetIn2; + private org.onosproject.p4runtime.ctl.controller.PacketInEvent packetIn; + private org.onosproject.p4runtime.ctl.controller.PacketInEvent sameAsPacketIn; + private org.onosproject.p4runtime.ctl.controller.PacketInEvent packetIn2; private PacketInEvent packetIn3; /** @@ -59,29 +60,27 @@ public class PacketInEventTest { public void setup() throws ImmutableByteSequence.ByteSequenceTrimException { packetOperation = PiPacketOperation.builder() - .forDevice(deviceId) .withData(ImmutableByteSequence.ofOnes(512)) .withType(PACKET_OUT) - .withMetadata(PiControlMetadata.builder() - .withId(PiControlMetadataId.of("egress_port")) + .withMetadata(PiPacketMetadata.builder() + .withId(PiPacketMetadataId.of("egress_port")) .withValue(copyFrom(DEFAULT_ORIGINAL_VALUE).fit(DEFAULT_BIT_WIDTH)) .build()) .build(); packetOperation2 = PiPacketOperation.builder() - .forDevice(deviceId2) .withData(ImmutableByteSequence.ofOnes(512)) .withType(PACKET_IN) - .withMetadata(PiControlMetadata.builder() - .withId(PiControlMetadataId.of("ingress_port")) + .withMetadata(PiPacketMetadata.builder() + .withId(PiPacketMetadataId.of("ingress_port")) .withValue(copyFrom(DEFAULT_ORIGINAL_VALUE).fit(DEFAULT_BIT_WIDTH)) .build()) .build(); - packetIn = new PacketInEvent(deviceId, packetOperation); - sameAsPacketIn = new PacketInEvent(sameDeviceId, packetOperation); - packetIn2 = new PacketInEvent(deviceId2, packetOperation); - packetIn3 = new PacketInEvent(deviceId, packetOperation2); + packetIn = new org.onosproject.p4runtime.ctl.controller.PacketInEvent(deviceId, packetOperation); + sameAsPacketIn = new org.onosproject.p4runtime.ctl.controller.PacketInEvent(sameDeviceId, packetOperation); + packetIn2 = new org.onosproject.p4runtime.ctl.controller.PacketInEvent(deviceId2, packetOperation); + packetIn3 = new org.onosproject.p4runtime.ctl.controller.PacketInEvent(deviceId, packetOperation2); } /** @@ -105,7 +104,7 @@ public class PacketInEventTest { @Test(expected = NullPointerException.class) public void testConstructorWithNullDeviceId() { - new PacketInEvent(nullDeviceId, packetOperation); + new org.onosproject.p4runtime.ctl.controller.PacketInEvent(nullDeviceId, packetOperation); } /** @@ -114,7 +113,7 @@ public class PacketInEventTest { @Test(expected = NullPointerException.class) public void testConstructorWithNullPacketOperation() { - new PacketInEvent(deviceId, nullPacketOperation); + new org.onosproject.p4runtime.ctl.controller.PacketInEvent(deviceId, nullPacketOperation); } /** diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/codec/TableEntryEncoderTest.java similarity index 88% rename from protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java rename to protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/codec/TableEntryEncoderTest.java index 278e050b61..0619f6a748 100644 --- a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java +++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/codec/TableEntryEncoderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-present Open Networking Foundation + * Copyright 2019-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. @@ -14,9 +14,8 @@ * limitations under the License. */ -package org.onosproject.p4runtime.ctl; +package org.onosproject.p4runtime.ctl.codec; -import com.google.common.collect.Lists; import com.google.common.testing.EqualsTester; import org.easymock.EasyMock; import org.junit.Test; @@ -37,22 +36,20 @@ import org.onosproject.net.pi.runtime.PiExactFieldMatch; import org.onosproject.net.pi.runtime.PiMatchKey; import org.onosproject.net.pi.runtime.PiTableEntry; import org.onosproject.net.pi.runtime.PiTernaryFieldMatch; +import org.onosproject.p4runtime.ctl.utils.P4InfoBrowser; +import org.onosproject.p4runtime.ctl.utils.PipeconfHelper; import p4.v1.P4RuntimeOuterClass.Action; import p4.v1.P4RuntimeOuterClass.CounterData; import p4.v1.P4RuntimeOuterClass.TableEntry; import java.net.URL; -import java.util.Collection; import java.util.Random; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.onlab.util.ImmutableByteSequence.copyFrom; import static org.onlab.util.ImmutableByteSequence.ofOnes; import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT; -import static org.onosproject.p4runtime.ctl.TableEntryEncoder.decode; -import static org.onosproject.p4runtime.ctl.TableEntryEncoder.encode; /** * Test for P4 runtime table entry encoder. @@ -169,13 +166,10 @@ public class TableEntryEncoderTest { @Test public void testTableEntryEncoder() throws Exception { - Collection result = encode(Lists.newArrayList(piTableEntry), defaultPipeconf); - assertThat(result, hasSize(1)); - - TableEntry tableEntryMsg = result.iterator().next(); - - Collection decodedResults = decode(Lists.newArrayList(tableEntryMsg), defaultPipeconf); - PiTableEntry decodedPiTableEntry = decodedResults.iterator().next(); + TableEntry tableEntryMsg = Codecs.CODECS.tableEntry().encode( + piTableEntry, null, defaultPipeconf); + PiTableEntry decodedPiTableEntry = Codecs.CODECS.tableEntry().decode( + tableEntryMsg, null, defaultPipeconf); // Test equality for decoded entry. new EqualsTester() @@ -218,13 +212,10 @@ public class TableEntryEncoderTest { @Test public void testActopProfileGroup() throws Exception { - Collection result = encode(Lists.newArrayList(piTableEntryWithGroupAction), defaultPipeconf); - assertThat(result, hasSize(1)); - - TableEntry tableEntryMsg = result.iterator().next(); - - Collection decodedResults = decode(Lists.newArrayList(tableEntryMsg), defaultPipeconf); - PiTableEntry decodedPiTableEntry = decodedResults.iterator().next(); + TableEntry tableEntryMsg = Codecs.CODECS.tableEntry().encode( + piTableEntryWithGroupAction, null, defaultPipeconf); + PiTableEntry decodedPiTableEntry = Codecs.CODECS.tableEntry().decode( + tableEntryMsg, null, defaultPipeconf); // Test equality for decoded entry. new EqualsTester() @@ -247,13 +238,10 @@ public class TableEntryEncoderTest { @Test public void testEncodeWithNoAction() throws Exception { - Collection result = encode(Lists.newArrayList(piTableEntryWithoutAction), defaultPipeconf); - assertThat(result, hasSize(1)); - - TableEntry tableEntryMsg = result.iterator().next(); - - Collection decodedResults = decode(Lists.newArrayList(tableEntryMsg), defaultPipeconf); - PiTableEntry decodedPiTableEntry = decodedResults.iterator().next(); + TableEntry tableEntryMsg = Codecs.CODECS.tableEntry().encode( + piTableEntryWithoutAction, null, defaultPipeconf); + PiTableEntry decodedPiTableEntry = Codecs.CODECS.tableEntry().decode( + tableEntryMsg, null, defaultPipeconf); // Test equality for decoded entry. new EqualsTester() diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ControlMetadataModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ControlMetadataModel.java index 45e7edc8ad..a6e2a43b34 100644 --- a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ControlMetadataModel.java +++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ControlMetadataModel.java @@ -16,26 +16,26 @@ package org.onosproject.p4runtime.model; -import org.onosproject.net.pi.model.PiControlMetadataId; -import org.onosproject.net.pi.model.PiControlMetadataModel; +import org.onosproject.net.pi.model.PiPacketMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataModel; import java.util.Objects; /** - * Implementation of PiControlMetadataModel for P4Runtime. + * Implementation of PiPacketMetadataModel for P4Runtime. */ -final class P4ControlMetadataModel implements PiControlMetadataModel { +final class P4PacketMetadataModel implements PiPacketMetadataModel { - private final PiControlMetadataId id; + private final PiPacketMetadataId id; private final int bitWidth; - P4ControlMetadataModel(PiControlMetadataId id, int bitWidth) { + P4PacketMetadataModel(PiPacketMetadataId id, int bitWidth) { this.id = id; this.bitWidth = bitWidth; } @Override - public PiControlMetadataId id() { + public PiPacketMetadataId id() { return id; } @@ -57,7 +57,7 @@ final class P4ControlMetadataModel implements PiControlMetadataModel { if (obj == null || getClass() != obj.getClass()) { return false; } - final P4ControlMetadataModel other = (P4ControlMetadataModel) obj; + final P4PacketMetadataModel other = (P4PacketMetadataModel) obj; return Objects.equals(this.id, other.id) && Objects.equals(this.bitWidth, other.bitWidth); } diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java index c69cd0b97a..61608811e1 100644 --- a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java +++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java @@ -28,8 +28,8 @@ import org.onosproject.net.pi.model.PiActionParamId; import org.onosproject.net.pi.model.PiActionParamModel; import org.onosproject.net.pi.model.PiActionProfileId; import org.onosproject.net.pi.model.PiActionProfileModel; -import org.onosproject.net.pi.model.PiControlMetadataId; -import org.onosproject.net.pi.model.PiControlMetadataModel; +import org.onosproject.net.pi.model.PiPacketMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataModel; import org.onosproject.net.pi.model.PiCounterId; import org.onosproject.net.pi.model.PiCounterModel; import org.onosproject.net.pi.model.PiCounterType; @@ -362,10 +362,10 @@ public final class P4InfoParser { throws P4InfoParserException { final Map packetOpMap = Maps.newHashMap(); for (ControllerPacketMetadata ctrlPktMetaMsg : p4info.getControllerPacketMetadataList()) { - final ImmutableList.Builder metadataListBuilder = + final ImmutableList.Builder metadataListBuilder = ImmutableList.builder(); ctrlPktMetaMsg.getMetadataList().forEach(metadataMsg -> metadataListBuilder.add( - new P4ControlMetadataModel(PiControlMetadataId.of(metadataMsg.getName()), + new P4PacketMetadataModel(PiPacketMetadataId.of(metadataMsg.getName()), metadataMsg.getBitwidth()))); packetOpMap.put( mapPacketOpType(ctrlPktMetaMsg.getPreamble().getName()), diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4PacketOperationModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4PacketOperationModel.java index 84e2fe412f..e29553fec9 100644 --- a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4PacketOperationModel.java +++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4PacketOperationModel.java @@ -17,7 +17,7 @@ package org.onosproject.p4runtime.model; import com.google.common.collect.ImmutableList; -import org.onosproject.net.pi.model.PiControlMetadataModel; +import org.onosproject.net.pi.model.PiPacketMetadataModel; import org.onosproject.net.pi.model.PiPacketOperationModel; import org.onosproject.net.pi.model.PiPacketOperationType; @@ -30,10 +30,10 @@ import java.util.Objects; final class P4PacketOperationModel implements PiPacketOperationModel { private final PiPacketOperationType type; - private final ImmutableList metadatas; + private final ImmutableList metadatas; P4PacketOperationModel(PiPacketOperationType type, - ImmutableList metadatas) { + ImmutableList metadatas) { this.type = type; this.metadatas = metadatas; } @@ -44,7 +44,7 @@ final class P4PacketOperationModel implements PiPacketOperationModel { } @Override - public List metadatas() { + public List metadatas() { return metadatas; } diff --git a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4ControlMetadataModelTest.java b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PacketMetadataModelTest.java similarity index 61% rename from protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4ControlMetadataModelTest.java rename to protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PacketMetadataModelTest.java index 998875acc4..ca5f131b30 100644 --- a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4ControlMetadataModelTest.java +++ b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PacketMetadataModelTest.java @@ -17,32 +17,31 @@ package org.onosproject.p4runtime.model; import com.google.common.testing.EqualsTester; import org.junit.Test; -import org.onosproject.net.pi.model.PiControlMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataId; -import static org.junit.Assert.*; import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable; /** - * Test for P4ControlMetadataModel class. + * Test for P4PacketMetadataModel class. */ -public class P4ControlMetadataModelTest { +public class P4PacketMetadataModelTest { - private final PiControlMetadataId piControlMetadataId = PiControlMetadataId.of("EGRESS_PORT"); - private final PiControlMetadataId sameAsPiControlMetadataId = PiControlMetadataId.of("EGRESS_PORT"); - private final PiControlMetadataId piControlMetadataId2 = PiControlMetadataId.of("INGRESS_PORT"); + private final PiPacketMetadataId piPacketMetadataId = PiPacketMetadataId.of("EGRESS_PORT"); + private final PiPacketMetadataId sameAsPiPacketMetadataId = PiPacketMetadataId.of("EGRESS_PORT"); + private final PiPacketMetadataId piPacketMetadataId2 = PiPacketMetadataId.of("INGRESS_PORT"); private static final int BIT_WIDTH_32 = 32; private static final int BIT_WIDTH_64 = 64; - private final P4ControlMetadataModel metadataModel = new P4ControlMetadataModel(piControlMetadataId, BIT_WIDTH_32); + private final P4PacketMetadataModel metadataModel = new P4PacketMetadataModel(piPacketMetadataId, BIT_WIDTH_32); - private final P4ControlMetadataModel sameAsMetadataModel = new P4ControlMetadataModel(sameAsPiControlMetadataId, + private final P4PacketMetadataModel sameAsMetadataModel = new P4PacketMetadataModel(sameAsPiPacketMetadataId, BIT_WIDTH_32); - private final P4ControlMetadataModel metadataModel2 = new P4ControlMetadataModel(piControlMetadataId2, + private final P4PacketMetadataModel metadataModel2 = new P4PacketMetadataModel(piPacketMetadataId2, BIT_WIDTH_32); - private final P4ControlMetadataModel metadataModel3 = new P4ControlMetadataModel(piControlMetadataId, BIT_WIDTH_64); + private final P4PacketMetadataModel metadataModel3 = new P4PacketMetadataModel(piPacketMetadataId, BIT_WIDTH_64); @@ -51,7 +50,7 @@ public class P4ControlMetadataModelTest { */ @Test public void testImmutability() { - assertThatClassIsImmutable(P4ControlMetadataModel.class); + assertThatClassIsImmutable(P4PacketMetadataModel.class); } /** @@ -65,4 +64,4 @@ public class P4ControlMetadataModelTest { .addEqualityGroup(metadataModel3) .testEquals(); } -} \ No newline at end of file +} diff --git a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PacketOperationModelTest.java b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PacketOperationModelTest.java index 486a62544f..5d760e8e82 100644 --- a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PacketOperationModelTest.java +++ b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PacketOperationModelTest.java @@ -19,8 +19,8 @@ package org.onosproject.p4runtime.model; import com.google.common.collect.ImmutableList; import com.google.common.testing.EqualsTester; import org.junit.Test; -import org.onosproject.net.pi.model.PiControlMetadataId; -import org.onosproject.net.pi.model.PiControlMetadataModel; +import org.onosproject.net.pi.model.PiPacketMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataModel; import org.onosproject.net.pi.model.PiPacketOperationType; import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable; @@ -32,29 +32,29 @@ public class P4PacketOperationModelTest { private static final PiPacketOperationType PI_PACKET_OPERATION_TYPE_1 = PiPacketOperationType.PACKET_IN; private static final PiPacketOperationType PI_PACKET_OPERATION_TYPE_2 = PiPacketOperationType.PACKET_OUT; - private static final PiControlMetadataId PI_CONTROL_METADATA_ID_1 = PiControlMetadataId.of("Metadata1"); - private static final PiControlMetadataId PI_CONTROL_METADATA_ID_2 = PiControlMetadataId.of("Metadata2"); + private static final PiPacketMetadataId PI_CONTROL_METADATA_ID_1 = PiPacketMetadataId.of("Metadata1"); + private static final PiPacketMetadataId PI_CONTROL_METADATA_ID_2 = PiPacketMetadataId.of("Metadata2"); private static final int BIT_WIDTH_1 = 8; private static final int BIT_WIDTH_2 = 9; - private static final PiControlMetadataModel PI_CONTROL_METADATA_MODEL_1 = - new P4ControlMetadataModel(PI_CONTROL_METADATA_ID_1, BIT_WIDTH_1); - private static final PiControlMetadataModel PI_CONTROL_METADATA_MODEL_2 = - new P4ControlMetadataModel(PI_CONTROL_METADATA_ID_2, BIT_WIDTH_2); + private static final PiPacketMetadataModel PI_CONTROL_METADATA_MODEL_1 = + new P4PacketMetadataModel(PI_CONTROL_METADATA_ID_1, BIT_WIDTH_1); + private static final PiPacketMetadataModel PI_CONTROL_METADATA_MODEL_2 = + new P4PacketMetadataModel(PI_CONTROL_METADATA_ID_2, BIT_WIDTH_2); - private static final ImmutableList METADATAS_1 = - new ImmutableList.Builder() + private static final ImmutableList METADATAS_1 = + new ImmutableList.Builder() .add(PI_CONTROL_METADATA_MODEL_1) .build(); - private static final ImmutableList METADATAS_2 = - new ImmutableList.Builder() + private static final ImmutableList METADATAS_2 = + new ImmutableList.Builder() .add(PI_CONTROL_METADATA_MODEL_2) .build(); - private static final ImmutableList METADATAS_3 = - new ImmutableList.Builder() + private static final ImmutableList METADATAS_3 = + new ImmutableList.Builder() .add(PI_CONTROL_METADATA_MODEL_1) .add(PI_CONTROL_METADATA_MODEL_2) .build(); @@ -87,4 +87,4 @@ public class P4PacketOperationModelTest { .addEqualityGroup(P4_PACKET_OPERATION_MODEL_3) .testEquals(); } -} \ No newline at end of file +} diff --git a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PipelineModelTest.java b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PipelineModelTest.java index 4723ea5ac0..e8b1c70eb3 100644 --- a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PipelineModelTest.java +++ b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4PipelineModelTest.java @@ -27,8 +27,8 @@ import org.onosproject.net.pi.model.PiActionParamId; import org.onosproject.net.pi.model.PiActionParamModel; import org.onosproject.net.pi.model.PiActionProfileId; import org.onosproject.net.pi.model.PiActionProfileModel; -import org.onosproject.net.pi.model.PiControlMetadataId; -import org.onosproject.net.pi.model.PiControlMetadataModel; +import org.onosproject.net.pi.model.PiPacketMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataModel; import org.onosproject.net.pi.model.PiCounterId; import org.onosproject.net.pi.model.PiCounterModel; import org.onosproject.net.pi.model.PiCounterType; @@ -287,16 +287,16 @@ public class P4PipelineModelTest { private static final PiPacketOperationType PI_PACKET_OPERATION_TYPE_1 = PiPacketOperationType.PACKET_IN; private static final PiPacketOperationType PI_PACKET_OPERATION_TYPE_2 = PiPacketOperationType.PACKET_OUT; - private static final PiControlMetadataId PI_CONTROL_METADATA_ID_1 = PiControlMetadataId.of("INGRESS PORT"); - private static final PiControlMetadataId PI_CONTROL_METADATA_ID_2 = PiControlMetadataId.of("EGRESS PORT"); + private static final PiPacketMetadataId PI_CONTROL_METADATA_ID_1 = PiPacketMetadataId.of("INGRESS PORT"); + private static final PiPacketMetadataId PI_CONTROL_METADATA_ID_2 = PiPacketMetadataId.of("EGRESS PORT"); private static final int META_BIT_WIDTH_1 = 32; private static final int META_BIT_WIDTH_2 = 64; - private static final PiControlMetadataModel P4_CONTROL_METADATA_MODEL_1 = - new P4ControlMetadataModel(PI_CONTROL_METADATA_ID_1, META_BIT_WIDTH_1); - private static final PiControlMetadataModel P4_CONTROL_METADATA_MODEL_2 = - new P4ControlMetadataModel(PI_CONTROL_METADATA_ID_2, META_BIT_WIDTH_2); + private static final PiPacketMetadataModel P4_CONTROL_METADATA_MODEL_1 = + new P4PacketMetadataModel(PI_CONTROL_METADATA_ID_1, META_BIT_WIDTH_1); + private static final PiPacketMetadataModel P4_CONTROL_METADATA_MODEL_2 = + new P4PacketMetadataModel(PI_CONTROL_METADATA_ID_2, META_BIT_WIDTH_2); /* Pipeline Models */ private static final ImmutableMap TABLES_1 = @@ -317,12 +317,12 @@ public class P4PipelineModelTest { .put(PI_ACTION_PROFILE_ID_2, P4_ACTION_PROFILE_MODEL_2) .build(); - private static final ImmutableList METADATAS_1 = - new ImmutableList.Builder() + private static final ImmutableList METADATAS_1 = + new ImmutableList.Builder() .add(P4_CONTROL_METADATA_MODEL_1) .build(); - private static final ImmutableList METADATAS_2 = - new ImmutableList.Builder() + private static final ImmutableList METADATAS_2 = + new ImmutableList.Builder() .add(P4_CONTROL_METADATA_MODEL_2) .build(); diff --git a/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java b/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java index 22cfc02bf7..82e2979b69 100644 --- a/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java +++ b/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java @@ -17,11 +17,6 @@ package org.onosproject.provider.p4runtime.packet.impl; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; import org.onosproject.mastership.MastershipService; import org.onosproject.net.Device; import org.onosproject.net.DeviceId; @@ -44,6 +39,11 @@ import org.onosproject.p4runtime.api.P4RuntimeController; import org.onosproject.p4runtime.api.P4RuntimeEvent; import org.onosproject.p4runtime.api.P4RuntimeEventListener; import org.onosproject.p4runtime.api.P4RuntimePacketIn; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; import org.slf4j.Logger; import java.nio.ByteBuffer; @@ -177,7 +177,7 @@ public class P4RuntimePacketProvider extends AbstractProvider implements PacketP PiPacketOperation operation = eventSubject.packetOperation(); InboundPacket inPkt; try { - inPkt = device.as(PiPipelineInterpreter.class).mapInboundPacket(operation); + inPkt = device.as(PiPipelineInterpreter.class).mapInboundPacket(operation, deviceId); } catch (PiPipelineInterpreter.PiInterpreterException e) { log.warn("Unable to interpret inbound packet from {}: {}", deviceId, e.getMessage()); return; diff --git a/tools/dev/bin/onos-gen-p4-constants b/tools/dev/bin/onos-gen-p4-constants index 80f8f63d40..2f67201be0 100755 --- a/tools/dev/bin/onos-gen-p4-constants +++ b/tools/dev/bin/onos-gen-p4-constants @@ -26,7 +26,7 @@ imports = ''' import org.onosproject.net.pi.model.PiActionId; import org.onosproject.net.pi.model.PiActionParamId; import org.onosproject.net.pi.model.PiActionProfileId; -import org.onosproject.net.pi.model.PiControlMetadataId; +import org.onosproject.net.pi.model.PiPacketMetadataId; import org.onosproject.net.pi.model.PiCounterId; import org.onosproject.net.pi.model.PiMatchFieldId; import org.onosproject.net.pi.model.PiTableId;''' @@ -70,8 +70,8 @@ PI_ACT_PRM_ID_CST = 'PiActionParamId.of("%s")' PI_ACT_PROF_ID = 'PiActionProfileId' PI_ACT_PROF_ID_CST = 'PiActionProfileId.of("%s")' -PI_PKT_META_ID = 'PiControlMetadataId' -PI_PKT_META_ID_CST = 'PiControlMetadataId.of("%s")' +PI_PKT_META_ID = 'PiPacketMetadataId' +PI_PKT_META_ID_CST = 'PiPacketMetadataId.of("%s")' HF_VAR_PREFIX = 'HDR_'