New P4RuntimeClient implementation that supports batching and error reporting

The new client API supports batching and provides detailed response for
write requests (e.g. if entity already exists when inserting), which was
not possible with the old one.

This patch includes:
- New more efficient implementation of P4RuntimeClient (no more locking,
use native gRPC executor, use stub deadlines)
- Ported all codecs to new AbstractCodec-based implementation (needed to
implement codec cache in the future)
- Uses batching in P4RuntimeFlowRuleProgrammable and
P4RuntimeGroupActionProgrammable
- Minor changes to PI framework runtime classes

Change-Id: I3fac42057bb4e1389d761006a32600c786598683
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 ff00a1a..31db294 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<Boolean> 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<Boolean> 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<Boolean> 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.
-     * <p>
-     * 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<Boolean> writeTableEntries(
-            List<PiTableEntry> 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<List<PiTableEntry>> dumpTables(
-            Set<PiTableId> tableIds, boolean defaultEntries, PiPipeconf pipeconf);
-
-    /**
-     * Dumps entries from all tables, for the given pipeconf.
-     *
-     * @param pipeconf pipeconf currently deployed on the device
-     * @return completable future of a list of table entries
-     */
-    CompletableFuture<List<PiTableEntry>> 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<Boolean> 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<List<PiCounterCell>> readAllCounterCells(
-            Set<PiCounterId> 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<List<PiCounterCell>> readCounterCells(
-            Set<PiCounterCellId> 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<Boolean> writeActionProfileMembers(
-            List<PiActionProfileMember> 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<Boolean> 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<List<PiActionProfileGroup>> 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<List<PiActionProfileMember>> 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<List<PiActionProfileMemberId>> removeActionProfileMembers(
-            PiActionProfileId actionProfileId,
-            List<PiActionProfileMemberId> 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<List<PiMeterCellConfig>> readAllMeterCells(
-            Set<PiMeterId> 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<List<PiMeterCellConfig>> readMeterCells(
-            Set<PiMeterCellId> 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<Boolean> writeMeterCells(
-            List<PiMeterCellConfig> cellConfigs, PiPipeconf pipeconf);
-
-    /**
-     * Performs the given write operation for the given PI multicast groups
-     * entries.
-     *
-     * @param entries multicast group entries
-     * @param opType  write operation type
-     * @return true if the operation was successful, false otherwise
-     */
-    CompletableFuture<Boolean> writePreMulticastGroupEntries(
-            List<PiMulticastGroupEntry> entries,
-            WriteOperationType opType);
-
-    /**
-     * Returns all multicast groups on device.
-     *
-     * @return multicast groups
-     */
-    CompletableFuture<List<PiMulticastGroupEntry>> 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 0000000..34dac66
--- /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<Boolean> 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.
+     * <p>
+     * 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<Boolean> 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 0000000..9a47398
--- /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<? extends PiHandle> 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<PiTableId> 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<PiTableId> 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<PiActionProfileId> 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<PiActionProfileId> 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<PiCounterId> 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<PiTableId> 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<PiMeterId> 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<PiTableId> 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<ReadResponse> 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<PiEntity> all();
+
+        /**
+         * Returns a collection of all PI entities of a given class returned by
+         * the server.
+         *
+         * @param clazz PI entity class
+         * @param <E>   PI entity class
+         * @return collection of all PI entities returned by the server for the
+         * given PI entity class
+         */
+        <E extends PiEntity> Collection<E> all(Class<E> 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 0000000..1317e29
--- /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.
+     * <p>
+     * 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.
+     * <p>
+     * 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 0000000..6d8dbfe
--- /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<? extends PiEntity> 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<? extends PiEntity> 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<? extends PiHandle> 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<? extends PiEntity> 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<WriteResponse> 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<WriteEntityResponse> 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<WriteEntityResponse> 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<WriteEntityResponse> 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<WriteEntityResponse> 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();
+    }
+}