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/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 f86e70c..57e65a2 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.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 @@
         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 519a1c4..3579054 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 @@
  * defined by a device ID, action profile ID and group ID.
  */
 @Beta
-public final class PiActionProfileGroupHandle extends PiHandle<PiActionProfileGroup> {
+public final class PiActionProfileGroupHandle extends PiHandle {
 
     private final PiActionProfileId actionProfileId;
     private final PiActionProfileGroupId groupId;
@@ -39,10 +39,11 @@
     }
 
     /**
-     * Creates a new handle for the given device ID and PI action profile group.
+     * Creates a new handle for the given device ID and PI action profile
+     * group.
      *
      * @param deviceId device ID
-     * @param group PI action profile group
+     * @param group    PI action profile group
      * @return PI action profile group handle
      */
     public static PiActionProfileGroupHandle of(DeviceId deviceId,
@@ -50,6 +51,24 @@
         return new PiActionProfileGroupHandle(deviceId, group);
     }
 
+    /**
+     * Returns the action profile ID of this handle.
+     *
+     * @return action profile ID
+     */
+    public PiActionProfileId actionProfile() {
+        return actionProfileId;
+    }
+
+    /**
+     * Returns the group ID of this handle.
+     *
+     * @return group ID
+     */
+    public PiActionProfileGroupId groupId() {
+        return groupId;
+    }
+
     @Override
     public PiEntityType entityType() {
         return PiEntityType.ACTION_PROFILE_GROUP;
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileMember.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileMember.java
index b5df9d0..d850943 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileMember.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileMember.java
@@ -19,6 +19,7 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.pi.model.PiActionProfileId;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -74,6 +75,11 @@
     }
 
     @Override
+    public PiActionProfileMemberHandle handle(DeviceId deviceId) {
+        return PiActionProfileMemberHandle.of(deviceId, this);
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) {
             return true;
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileMemberHandle.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileMemberHandle.java
index 40cb960..7dda091 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileMemberHandle.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileMemberHandle.java
@@ -27,7 +27,7 @@
  * Global identifier of a PI action profile member, uniquely defined by a
  * device ID, action profile ID, and member ID.
  */
-public final class PiActionProfileMemberHandle extends PiHandle<PiActionProfileMember> {
+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 9508cbc..34ebd7b 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 @@
 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 @@
     }
 
     /**
-     * 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;
@@ -71,6 +74,16 @@
     }
 
     @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) {
             return true;
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 0000000..c06fa52
--- /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 c3d5a01..531a6a2 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 @@
      * @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 4c31830..0e99188 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 @@
     /**
      * 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 eb74288..c959096 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 @@
  * the whole network.
  */
 @Beta
-public abstract class PiHandle<E extends PiEntity> {
+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 a978bfa..4ce849f 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 @@
 @Beta
 public final class PiMatchKey {
 
-    public static final PiMatchKey EMPTY = builder().build();
+    public static final PiMatchKey EMPTY = new PiMatchKey(ImmutableMap.of());
 
     private final ImmutableMap<PiMatchFieldId, PiFieldMatch> 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 efa53ff..b1dbcf5 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 @@
  * 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 96ba124..f13e276 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.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;
@@ -71,6 +72,11 @@
     }
 
     @Override
+    public PiMeterCellHandle handle(DeviceId deviceId) {
+        return PiMeterCellHandle.of(deviceId, this);
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) {
             return true;
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 4baa6fa..382f60b 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 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<PiMeterCellConfig> {
+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 @@
      * @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 @@
      * @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 @@
         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 41b93f6..7a833aa 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.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;
@@ -71,6 +72,11 @@
     }
 
     @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 65a3f28..b74ca8e 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 @@
  * ID.
  */
 @Beta
-public final class PiMulticastGroupEntryHandle extends PiHandle<PiMulticastGroupEntry> {
+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 @@
      * @return PI multicast group entry handle
      */
     public static PiMulticastGroupEntryHandle of(DeviceId deviceId,
-                                                 long groupId) {
+                                                 int groupId) {
         return new PiMulticastGroupEntryHandle(deviceId, groupId);
     }
 
@@ -64,6 +64,15 @@
         return new PiMulticastGroupEntryHandle(deviceId, entry.groupId());
     }
 
+    /**
+     * Returns the multicast group ID associated with this handle.
+     *
+     * @return group ID
+     */
+    public int groupId() {
+        return groupId;
+    }
+
     @Override
     public PiEntityType entityType() {
         return PiEntityType.PRE_MULTICAST_GROUP_ENTRY;
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiControlMetadata.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketMetadata.java
similarity index 67%
rename from core/api/src/main/java/org/onosproject/net/pi/runtime/PiControlMetadata.java
rename to core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketMetadata.java
index 3caa71c..385f099 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiControlMetadata.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketMetadata.java
@@ -19,36 +19,40 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import org.onlab.util.ImmutableByteSequence;
-import org.onosproject.net.pi.model.PiControlMetadataId;
+import org.onosproject.net.pi.model.PiPacketMetadataId;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Instance of a control metadata for a protocol-independent pipeline.
+ * Instance of a metadata field for a controller packet-in/out for a
+ * protocol-independent pipeline. Metadata are used to carry information other
+ * than the packet-in/out payload, such as the original ingress port of a
+ * packet-in, or the egress port of packet-out.
  */
 @Beta
-public final class PiControlMetadata {
+public final class PiPacketMetadata {
 
-    private final PiControlMetadataId id;
+    private final PiPacketMetadataId id;
     private final ImmutableByteSequence value;
 
     /**
-     * Creates a new control metadata instance for the given identifier and value.
+     * Creates a new packet metadata instance for the given identifier and
+     * value.
      *
-     * @param id    control metadata identifier
+     * @param id    packet metadata identifier
      * @param value value for this metadata
      */
-    private PiControlMetadata(PiControlMetadataId id, ImmutableByteSequence value) {
+    private PiPacketMetadata(PiPacketMetadataId id, ImmutableByteSequence value) {
         this.id = id;
         this.value = value;
     }
 
     /**
-     * Return the identifier of this control metadata.
+     * Return the identifier of this packet metadata.
      *
-     * @return control metadata identifier
+     * @return packet metadata identifier
      */
-    public PiControlMetadataId id() {
+    public PiPacketMetadataId id() {
         return id;
     }
 
@@ -69,7 +73,7 @@
         if (o == null || getClass() != o.getClass()) {
             return false;
         }
-        PiControlMetadata piPacket = (PiControlMetadata) o;
+        PiPacketMetadata piPacket = (PiPacketMetadata) o;
         return Objects.equal(id, piPacket.id()) &&
                 Objects.equal(value, piPacket.value());
     }
@@ -85,7 +89,7 @@
     }
 
     /**
-     * Returns a control metadata builder.
+     * Returns a packet metadata builder.
      *
      * @return a new builder
      */
@@ -94,11 +98,11 @@
     }
 
     /**
-     * Builder of protocol-independent control metadatas.
+     * Builder of protocol-independent packet metadatas.
      */
     public static final class Builder {
 
-        private PiControlMetadataId id;
+        private PiPacketMetadataId id;
         private ImmutableByteSequence value;
 
         private Builder() {
@@ -106,12 +110,12 @@
         }
 
         /**
-         * Sets the identifier of this control metadata.
+         * Sets the identifier of this packet metadata.
          *
-         * @param id control metadata identifier
+         * @param id packet metadata identifier
          * @return this
          */
-        public Builder withId(PiControlMetadataId id) {
+        public Builder withId(PiPacketMetadataId id) {
             this.id = id;
             return this;
         }
@@ -128,14 +132,14 @@
         }
 
         /**
-         * Returns a new control metadata instance.
+         * Returns a new packet metadata instance.
          *
-         * @return control metadata
+         * @return packet metadata
          */
-        public PiControlMetadata build() {
+        public PiPacketMetadata build() {
             checkNotNull(id);
             checkNotNull(value);
-            return new PiControlMetadata(id, value);
+            return new PiPacketMetadata(id, value);
         }
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketOperation.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketOperation.java
index 74c68d6..5811b97 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketOperation.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketOperation.java
@@ -21,8 +21,7 @@
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableSet;
 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 org.onosproject.net.pi.model.PiPacketOperationType;
 
 import java.util.Collection;
@@ -33,43 +32,33 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Instance of a packet I/O operation, and its control metadatas, for a protocol-independent pipeline.
+ * Instance of a packet I/O operation that includes the packet body (frame) and
+ * its metadata, for a protocol-independent pipeline.
  */
 @Beta
 public final class PiPacketOperation {
 
-    private final DeviceId deviceId;
-    private final ImmutableByteSequence data;
-    private final Set<PiControlMetadata> packetMetadatas;
+    private final ImmutableByteSequence frame;
+    private final Set<PiPacketMetadata> 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<PiControlMetadata> packetMetadatas,
+    private PiPacketOperation(ImmutableByteSequence frame,
+                              Collection<PiPacketMetadata> 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.
      *
      * @return packet type
@@ -84,15 +73,16 @@
      * @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<PiControlMetadata> metadatas() {
+    public Collection<PiPacketMetadata> metadatas() {
         return packetMetadatas;
     }
 
@@ -106,22 +96,21 @@
         }
         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 static final class Builder {
 
-        private DeviceId deviceId;
-        private Map<PiControlMetadataId, PiControlMetadata> packetMetadatas = new HashMap<>();
+        private Map<PiPacketMetadataId, PiPacketMetadata> packetMetadatas = new HashMap<>();
         private PiPacketOperationType type;
         private ImmutableByteSequence data;
 
@@ -149,18 +137,6 @@
         }
 
         /**
-         * 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.
          *
          * @param data the packet raw data
@@ -173,13 +149,14 @@
         }
 
         /**
-         * 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 @@
          * @param metadatas collection of metadata
          * @return this
          */
-        public Builder withMetadatas(Collection<PiControlMetadata> metadatas) {
+        public Builder withMetadatas(Collection<PiPacketMetadata> metadatas) {
             checkNotNull(metadatas);
             metadatas.forEach(this::withMetadata);
             return this;
@@ -215,11 +192,10 @@
          * @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 4f65e4d..3a03feb 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 @@
  * 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 fd354ec..c50771b 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 @@
 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;
@@ -61,6 +62,12 @@
     }
 
     @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) {
             return true;
@@ -137,4 +144,4 @@
             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 b25ada5..ee3c3c3 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 @@
 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 @@
      *
      * @return optional priority
      */
-    public Optional<Integer> 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 @@
         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 2b210a1..b4edb3c 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 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 @@
  * by a device ID, table ID and match key.
  */
 @Beta
-public final class PiTableEntryHandle extends PiHandle<PiTableEntry> {
+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 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 @@
 
     @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 @@
         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 @@
                 .add("deviceId", deviceId())
                 .add("tableId", tableId)
                 .add("matchKey", matchKey)
+                .add("priority", priority == NO_PRIORITY ? "N/A" : priority)
                 .toString();
     }
 }