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/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();
+    }
+}