Merge remote-tracking branch 'origin/master' into dev/murrelet
diff --git a/core/api/src/main/java/org/onosproject/net/device/DefaultPortStatistics.java b/core/api/src/main/java/org/onosproject/net/device/DefaultPortStatistics.java
index 8827ecc..6e0ab42 100644
--- a/core/api/src/main/java/org/onosproject/net/device/DefaultPortStatistics.java
+++ b/core/api/src/main/java/org/onosproject/net/device/DefaultPortStatistics.java
@@ -16,6 +16,7 @@
 package org.onosproject.net.device;
 
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 
 /**
  * Default implementation of immutable port statistics.
@@ -23,7 +24,7 @@
 public final class DefaultPortStatistics implements PortStatistics {
 
     private final DeviceId deviceId;
-    private final int  port;
+    private final PortNumber portNumber;
     private final long packetsReceived;
     private final long packetsSent;
     private final long bytesReceived;
@@ -36,7 +37,7 @@
     private final long durationNano;
 
     private DefaultPortStatistics(DeviceId deviceId,
-                                  int  port,
+                                  PortNumber portNumber,
                                   long packetsReceived,
                                   long packetsSent,
                                   long bytesReceived,
@@ -48,7 +49,7 @@
                                   long durationSec,
                                   long durationNano) {
         this.deviceId = deviceId;
-        this.port = port;
+        this.portNumber = portNumber;
         this.packetsReceived = packetsReceived;
         this.packetsSent = packetsSent;
         this.bytesReceived = bytesReceived;
@@ -64,7 +65,7 @@
     // Constructor for serializer
     private DefaultPortStatistics() {
         this.deviceId = null;
-        this.port = 0;
+        this.portNumber = null;
         this.packetsReceived = 0;
         this.packetsSent = 0;
         this.bytesReceived = 0;
@@ -88,7 +89,12 @@
 
     @Override
     public int port() {
-        return this.port;
+        return (int) this.portNumber.toLong();
+    }
+
+    @Override
+    public PortNumber portNumber() {
+        return this.portNumber;
     }
 
     @Override
@@ -154,7 +160,7 @@
     @Override
     public String toString() {
         return "device: " + deviceId + ", " +
-                "port: " + this.port + ", " +
+                "port: " + this.portNumber + ", " +
                 "pktRx: " + this.packetsReceived + ", " +
                 "pktTx: " + this.packetsSent + ", " +
                 "byteRx: " + this.bytesReceived + ", " +
@@ -168,7 +174,7 @@
     public static final class Builder {
 
         DeviceId deviceId;
-        int port;
+        PortNumber portNumber;
         long packetsReceived;
         long packetsSent;
         long bytesReceived;
@@ -189,9 +195,23 @@
          *
          * @param port port number
          * @return builder object
+         * @deprecated ONOS 1.12 Magpie
          */
+        @Deprecated
         public Builder setPort(int port) {
-            this.port = port;
+            this.portNumber = PortNumber.portNumber(port);
+
+            return this;
+        }
+
+        /**
+         * Sets port number.
+         *
+         * @param portNumber port number
+         * @return builder object
+         */
+        public Builder setPort(PortNumber portNumber) {
+            this.portNumber = portNumber;
 
             return this;
         }
@@ -336,7 +356,7 @@
         public DefaultPortStatistics build() {
             return new DefaultPortStatistics(
                     deviceId,
-                    port,
+                    portNumber,
                     packetsReceived,
                     packetsSent,
                     bytesReceived,
diff --git a/core/api/src/main/java/org/onosproject/net/device/PortStatistics.java b/core/api/src/main/java/org/onosproject/net/device/PortStatistics.java
index 25fb057..3b9f19a 100644
--- a/core/api/src/main/java/org/onosproject/net/device/PortStatistics.java
+++ b/core/api/src/main/java/org/onosproject/net/device/PortStatistics.java
@@ -15,6 +15,8 @@
  */
 package org.onosproject.net.device;
 
+import org.onosproject.net.PortNumber;
+
 /**
  * Statistics of a port.
  */
@@ -24,10 +26,19 @@
      * Returns the port number.
      *
      * @return port number
+     * @deprecated ONOS 1.12 Magpie please use portNumber()
      */
+    @Deprecated
     int  port();
 
     /**
+     * Returns the port number.
+     *
+     * @return port number
+     */
+    PortNumber portNumber();
+
+    /**
      * Returns the number of packets received.
      *
      * @return the number of packets received
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellData.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellData.java
new file mode 100644
index 0000000..f0aada5
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellData.java
@@ -0,0 +1,100 @@
+/*
+ * 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.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+/**
+ * Data of a counter cell of a protocol-independent pipeline.
+ */
+@Beta
+public final class PiCounterCellData {
+
+    private final PiCounterCellId cellId;
+    private final long packets;
+    private final long bytes;
+
+    /**
+     * Creates a new counter cell data 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
+     */
+    public PiCounterCellData(PiCounterCellId cellId, long packets, long bytes) {
+        this.cellId = cellId;
+        this.packets = packets;
+        this.bytes = bytes;
+    }
+
+    /**
+     * Returns the cell identifier.
+     *
+     * @return cell identifier
+     */
+    public PiCounterCellId cellId() {
+        return cellId;
+    }
+
+    /**
+     * Returns the packet count value contained by this cell.
+     *
+     * @return number of packets
+     */
+    public long packets() {
+        return packets;
+    }
+
+    /**
+     * Returns the byte count value contained by this cell.
+     *
+     * @return number of bytes
+     */
+    public long bytes() {
+        return bytes;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof PiCounterCellData)) {
+            return false;
+        }
+        PiCounterCellData that = (PiCounterCellData) o;
+        return packets == that.packets &&
+                bytes == that.bytes &&
+                Objects.equal(cellId, that.cellId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(cellId, packets, bytes);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("cellId", cellId)
+                .add("packets", packets)
+                .add("bytes", bytes)
+                .toString();
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellId.java
new file mode 100644
index 0000000..d762f23
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellId.java
@@ -0,0 +1,92 @@
+/*
+ * 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.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
+import org.onlab.util.Identifier;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Identifier of a counter cell of a protocol-independent pipeline.
+ */
+@Beta
+public final class PiCounterCellId extends Identifier<String> {
+
+    private final PiCounterId counterId;
+    private final long index;
+
+    private PiCounterCellId(PiCounterId counterId, long index) {
+        super(counterId.id() + "[" + index + "]");
+        this.counterId = counterId;
+        this.index = index;
+    }
+
+    /**
+     * Returns a counter cell identifier for the given counter identifier and index.
+     *
+     * @param counterId counter identifier
+     * @param index     index
+     * @return counter cell identifier
+     */
+    public static PiCounterCellId of(PiCounterId counterId, long index) {
+        checkNotNull(counterId);
+        checkArgument(index >= 0, "Index must be a positive integer");
+        return new PiCounterCellId(counterId, index);
+    }
+
+    /**
+     * Returns the counter identifier of this cell.
+     *
+     * @return counter identifier
+     */
+    public PiCounterId counterId() {
+        return counterId;
+    }
+
+    /**
+     * Returns the index of this cell.
+     *
+     * @return cell index
+     */
+    public long index() {
+        return index;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof PiCounterCellId)) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+        PiCounterCellId that = (PiCounterCellId) o;
+        return index == that.index &&
+                Objects.equal(counterId, that.counterId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(super.hashCode(), counterId, index);
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterId.java
new file mode 100644
index 0000000..6fcd55e
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterId.java
@@ -0,0 +1,55 @@
+/*
+ * 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.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+import org.onlab.util.Identifier;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Identifier of a counter of a protocol-independent pipeline.
+ */
+@Beta
+public final class PiCounterId extends Identifier<String> {
+
+    private PiCounterId(String name) {
+        super(name);
+    }
+
+    /**
+     * Returns a counter identifier for the given name.
+     *
+     * @param name counter name
+     * @return counter identifier
+     */
+    public static PiCounterId of(String name) {
+        checkNotNull(name);
+        checkArgument(!name.isEmpty(), "Name name can't be empty");
+        return new PiCounterId(name);
+    }
+
+    /**
+     * Returns the name of the counter.
+     *
+     * @return counter name
+     */
+    public String name() {
+        return this.identifier;
+    }
+}
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java
index 3dba8b6..41fe160 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPipeconfFactory.java
@@ -19,6 +19,7 @@
 import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
 import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
 import org.onosproject.drivers.p4runtime.DefaultP4Interpreter;
+import org.onosproject.drivers.p4runtime.DefaultP4PortStatisticsDiscovery;
 import org.onosproject.net.behaviour.Pipeliner;
 import org.onosproject.net.device.PortStatisticsDiscovery;
 import org.onosproject.net.pi.model.DefaultPiPipeconf;
@@ -60,7 +61,7 @@
                 .withPipelineModel(Bmv2PipelineModelParser.parse(jsonUrl))
                 .addBehaviour(PiPipelineInterpreter.class, DefaultP4Interpreter.class)
                 .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
-                .addBehaviour(PortStatisticsDiscovery.class, Bmv2DefaultPortStatisticsDiscovery.class)
+                .addBehaviour(PortStatisticsDiscovery.class, DefaultP4PortStatisticsDiscovery.class)
                 .addExtension(P4_INFO_TEXT, p4InfoUrl)
                 .addExtension(BMV2_JSON, jsonUrl)
                 .build();
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPortStatisticsDiscovery.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPortStatisticsDiscovery.java
deleted file mode 100644
index 96081be..0000000
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultPortStatisticsDiscovery.java
+++ /dev/null
@@ -1,40 +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.drivers.bmv2;
-
-import com.google.common.collect.ImmutableList;
-import org.onosproject.net.device.PortStatistics;
-import org.onosproject.net.device.PortStatisticsDiscovery;
-import org.onosproject.net.driver.AbstractHandlerBehaviour;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collection;
-
-/**
- * Implementation of the behaviour for discovering the port statistics of a Bmv2 device with the default.p4 program.
- */
-public class Bmv2DefaultPortStatisticsDiscovery extends AbstractHandlerBehaviour implements PortStatisticsDiscovery {
-
-    private final Logger log = LoggerFactory.getLogger(getClass());
-
-    @Override
-    public Collection<PortStatistics> discoverPortStatistics() {
-        log.debug("Discovering Port Statistics for device {}", handler().data().deviceId());
-        return ImmutableList.of();
-    }
-}
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
new file mode 100644
index 0000000..8a5a2dd
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimeHandlerBehaviour.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.drivers.p4runtime;
+
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.netty.NettyChannelBuilder;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.p4runtime.api.P4RuntimeClient;
+import org.onosproject.p4runtime.api.P4RuntimeController;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract implementation of a behaviour handler for a P4Runtime device.
+ */
+public class AbstractP4RuntimeHandlerBehaviour extends AbstractHandlerBehaviour {
+
+    public static final String P4RUNTIME_SERVER_ADDR_KEY = "p4runtime_ip";
+    public static final String P4RUNTIME_SERVER_PORT_KEY = "p4runtime_port";
+    public static final String P4RUNTIME_DEVICE_ID_KEY = "p4runtime_deviceId";
+
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    // Initialized by setupBehaviour()
+    protected DeviceId deviceId;
+    protected DeviceService deviceService;
+    protected Device device;
+    protected P4RuntimeController controller;
+    protected PiPipeconf pipeconf;
+    protected P4RuntimeClient client;
+
+    /**
+     * Initializes this behaviour attributes. Returns true if the operation was 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.
+     *
+     * @return true if successful, false otherwise
+     */
+    protected boolean setupBehaviour() {
+        deviceId = handler().data().deviceId();
+
+        deviceService = handler().get(DeviceService.class);
+        device = deviceService.getDevice(deviceId);
+        if (device == null) {
+            log.warn("Unable to find device with id {}, aborting operation", deviceId);
+            return false;
+        }
+
+        controller = handler().get(P4RuntimeController.class);
+        if (!controller.hasClient(deviceId)) {
+            log.warn("Unable to find client for {}, aborting operation", deviceId);
+            return false;
+        }
+        client = controller.getClient(deviceId);
+
+        PiPipeconfService piPipeconfService = handler().get(PiPipeconfService.class);
+        if (!piPipeconfService.ofDevice(deviceId).isPresent() ||
+                !piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).isPresent()) {
+            log.warn("Unable to get the pipeconf of {}, aborting operation", deviceId);
+            return false;
+        }
+        pipeconf = piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).get();
+
+        return true;
+    }
+
+    /**
+     * Create a P4Runtime client for this device. Returns true if the operation was successful, false otherwise.
+     *
+     * @return true if successful, false otherwise
+     */
+    protected boolean createClient() {
+        deviceId = handler().data().deviceId();
+        controller = handler().get(P4RuntimeController.class);
+
+        String serverAddr = this.data().value(P4RUNTIME_SERVER_ADDR_KEY);
+        String serverPortString = this.data().value(P4RUNTIME_SERVER_PORT_KEY);
+        String p4DeviceIdString = this.data().value(P4RUNTIME_DEVICE_ID_KEY);
+
+        if (serverAddr == null || serverPortString == null || p4DeviceIdString == null) {
+            log.warn("Unable to create client for {}, missing driver data key (required is {}, {}, and {})",
+                     deviceId, P4RUNTIME_SERVER_ADDR_KEY, P4RUNTIME_SERVER_PORT_KEY, P4RUNTIME_DEVICE_ID_KEY);
+            return false;
+        }
+
+        ManagedChannelBuilder channelBuilder = NettyChannelBuilder
+                .forAddress(serverAddr, Integer.valueOf(serverPortString))
+                .usePlaintext(true);
+
+        if (!controller.createClient(deviceId, Long.parseUnsignedLong(p4DeviceIdString), channelBuilder)) {
+            log.warn("Unable to create client for {}, aborting operation", deviceId);
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4PortStatisticsDiscovery.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4PortStatisticsDiscovery.java
new file mode 100644
index 0000000..2946304
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4PortStatisticsDiscovery.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.drivers.p4runtime;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onosproject.net.device.DefaultPortStatistics;
+import org.onosproject.net.device.PortStatistics;
+import org.onosproject.net.device.PortStatisticsDiscovery;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCellId;
+import org.onosproject.net.pi.runtime.PiCounterId;
+
+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;
+
+/**
+ * Implementation of a PortStatisticsBehaviour that can be used for any P4 program based on default.p4 (i.e. those
+ * under onos/tools/test/p4src).
+ */
+public class DefaultP4PortStatisticsDiscovery extends AbstractP4RuntimeHandlerBehaviour
+        implements PortStatisticsDiscovery {
+
+    private static final PiCounterId INGRESS_COUNTER_ID = PiCounterId.of("ingress_port_counter");
+    private static final PiCounterId EGRESS_COUNTER_ID = PiCounterId.of("egress_port_counter");
+
+    @Override
+    public Collection<PortStatistics> discoverPortStatistics() {
+
+        if (!super.setupBehaviour()) {
+            return Collections.emptyList();
+        }
+
+        Map<Long, DefaultPortStatistics.Builder> portStatBuilders = Maps.newHashMap();
+
+        deviceService.getPorts(deviceId)
+                .forEach(p -> portStatBuilders.put(p.number().toLong(),
+                                                   DefaultPortStatistics.builder()
+                                                           .setPort((int) p.number().toLong())
+                                                           .setDeviceId(deviceId)));
+
+        Set<PiCounterCellId> counterCellIds = Sets.newHashSet();
+        portStatBuilders.keySet().forEach(p -> {
+            // Counter cell/index = port number.
+            counterCellIds.add(PiCounterCellId.of(INGRESS_COUNTER_ID, p));
+            counterCellIds.add(PiCounterCellId.of(EGRESS_COUNTER_ID, p));
+        });
+
+        Collection<PiCounterCellData> 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();
+        }
+
+        counterEntryResponse.forEach(counterEntry -> {
+            if (!portStatBuilders.containsKey(counterEntry.cellId().index())) {
+                log.warn("Unrecognized counter index {}, skipping", counterEntry);
+                return;
+            }
+            DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(counterEntry.cellId().index());
+            if (counterEntry.cellId().counterId().equals(INGRESS_COUNTER_ID)) {
+                statsBuilder.setPacketsReceived(counterEntry.packets());
+                statsBuilder.setBytesReceived(counterEntry.bytes());
+            } else if (counterEntry.cellId().counterId().equals(EGRESS_COUNTER_ID)) {
+                statsBuilder.setPacketsSent(counterEntry.packets());
+                statsBuilder.setBytesSent(counterEntry.bytes());
+            } else {
+                log.warn("Unrecognized counter ID {}, skipping", counterEntry);
+            }
+        });
+
+        return portStatBuilders
+                .values()
+                .stream()
+                .map(DefaultPortStatistics.Builder::build)
+                .collect(Collectors.toList());
+    }
+}
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 1a0b755..515640b 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,29 +19,19 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import org.onosproject.net.Device;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.driver.AbstractHandlerBehaviour;
 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.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.model.PiPipelineModel;
 import org.onosproject.net.pi.model.PiTableModel;
 import org.onosproject.net.pi.runtime.PiFlowRuleTranslationService;
-import org.onosproject.net.pi.runtime.PiPipeconfService;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.runtime.PiTableId;
-import org.onosproject.p4runtime.api.P4RuntimeClient;
 import org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType;
-import org.onosproject.p4runtime.api.P4RuntimeController;
 import org.onosproject.p4runtime.api.P4RuntimeFlowRuleWrapper;
 import org.onosproject.p4runtime.api.P4RuntimeTableEntryReference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -58,9 +48,9 @@
 import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.*;
 
 /**
- * Implementation of the flow rule programmable behaviour for BMv2.
+ * Implementation of the flow rule programmable behaviour for P4Runtime.
  */
-public class P4RuntimeFlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
+public class P4RuntimeFlowRuleProgrammable extends AbstractP4RuntimeHandlerBehaviour implements FlowRuleProgrammable {
 
     // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
     /*
@@ -75,8 +65,6 @@
      */
     private boolean checkEntryStoreBeforeUpdate = true;
 
-    private final Logger log = LoggerFactory.getLogger(getClass());
-
     // Needed to synchronize operations over the same table entry.
     private static final ConcurrentMap<P4RuntimeTableEntryReference, Lock> ENTRY_LOCKS = Maps.newConcurrentMap();
 
@@ -85,50 +73,31 @@
     private static final ConcurrentMap<P4RuntimeTableEntryReference, P4RuntimeFlowRuleWrapper> ENTRY_STORE =
             Maps.newConcurrentMap();
 
-    private DeviceId deviceId;
-    private P4RuntimeClient client;
-    private PiPipeconf pipeconf;
     private PiPipelineModel pipelineModel;
     private PiPipelineInterpreter interpreter;
     private PiFlowRuleTranslationService piFlowRuleTranslationService;
 
-    private boolean init() {
+    @Override
+    protected boolean setupBehaviour() {
 
-        deviceId = handler().data().deviceId();
-
-        P4RuntimeController controller = handler().get(P4RuntimeController.class);
-        if (!controller.hasClient(deviceId)) {
-            log.warn("Unable to find client for {}, aborting flow rule operation", deviceId);
+        if (!super.setupBehaviour()) {
             return false;
         }
 
-        PiPipeconfService piPipeconfService = handler().get(PiPipeconfService.class);
-        if (!piPipeconfService.ofDevice(deviceId).isPresent() ||
-                !piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).isPresent()) {
-            log.warn("Unable to get the pipeconf of {}", deviceId);
-            return false;
-        }
-
-        DeviceService deviceService = handler().get(DeviceService.class);
-        Device device = deviceService.getDevice(deviceId);
         if (!device.is(PiPipelineInterpreter.class)) {
             log.warn("Unable to get interpreter of {}", deviceId);
             return false;
         }
-
-        client = controller.getClient(deviceId);
-        pipeconf = piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).get();
-        pipelineModel = pipeconf.pipelineModel();
         interpreter = device.as(PiPipelineInterpreter.class);
+        pipelineModel = pipeconf.pipelineModel();
         piFlowRuleTranslationService = handler().get(PiFlowRuleTranslationService.class);
-
         return true;
     }
 
     @Override
     public Collection<FlowEntry> getFlowEntries() {
 
-        if (!init()) {
+        if (!setupBehaviour()) {
             return Collections.emptyList();
         }
 
@@ -201,7 +170,7 @@
 
     private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
 
-        if (!init()) {
+        if (!setupBehaviour()) {
             return Collections.emptyList();
         }
 
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 ee070eb..8416786 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
@@ -16,27 +16,17 @@
 
 package org.onosproject.drivers.p4runtime;
 
-import io.grpc.ManagedChannelBuilder;
-import io.grpc.netty.NettyChannelBuilder;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.MastershipRole;
 import org.onosproject.net.device.DeviceHandshaker;
-import org.onosproject.net.driver.AbstractHandlerBehaviour;
-import org.onosproject.net.driver.DriverData;
 import org.onosproject.p4runtime.api.P4RuntimeController;
-import org.slf4j.Logger;
 
 import java.util.concurrent.CompletableFuture;
 
-import static org.slf4j.LoggerFactory.getLogger;
-
 /**
- * Implementation of DeviceHandshaker for BMv2.
+ * Implementation of DeviceHandshaker for P4Runtime.
  */
-public class P4RuntimeHandshaker extends AbstractHandlerBehaviour
-        implements DeviceHandshaker {
-
-    private final Logger log = getLogger(getClass());
+public class P4RuntimeHandshaker extends AbstractP4RuntimeHandlerBehaviour implements DeviceHandshaker {
 
     // TODO: consider abstract class with empty connect method and  implementation into a protected one for reusability.
 
@@ -46,29 +36,7 @@
     }
 
     private boolean doConnect() {
-
-        P4RuntimeController controller = handler().get(P4RuntimeController.class);
-
-        DeviceId deviceId = handler().data().deviceId();
-        // DeviceKeyService deviceKeyService = handler().get(DeviceKeyService.class);
-        DriverData data = data();
-
-        String serverAddr = data.value("p4runtime_ip");
-        int serverPort = Integer.valueOf(data.value("p4runtime_port"));
-        long p4DeviceId = Long.parseUnsignedLong(data.value("p4runtime_deviceId"));
-
-        ManagedChannelBuilder channelBuilder = NettyChannelBuilder
-                .forAddress(serverAddr, serverPort)
-                .usePlaintext(true);
-
-        if (!controller.createClient(deviceId, p4DeviceId, channelBuilder)) {
-            log.warn("Unable to create P4runtime client for {}", deviceId);
-            return false;
-        }
-
-        // TODO: gNMI handling
-
-        return true;
+        return super.createClient();
     }
 
     @Override
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 7a11376..e21dd19 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
@@ -16,57 +16,29 @@
 
 package org.onosproject.drivers.p4runtime;
 
-import org.onosproject.net.Device;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.driver.AbstractHandlerBehaviour;
 import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.net.packet.PacketProgrammable;
-import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
-import org.onosproject.net.pi.runtime.PiPipeconfService;
-import org.onosproject.p4runtime.api.P4RuntimeClient;
-import org.onosproject.p4runtime.api.P4RuntimeController;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.Collection;
 
 /**
- * Packet Programmable behaviour for BMv2 devices.
+ * Implementation of PacketProgrammable behaviour for P4Runtime.
  */
-public class P4RuntimePacketProgrammable extends AbstractHandlerBehaviour implements PacketProgrammable {
-    private final Logger log = LoggerFactory.getLogger(getClass());
+public class P4RuntimePacketProgrammable extends AbstractP4RuntimeHandlerBehaviour implements PacketProgrammable {
 
     @Override
     public void emit(OutboundPacket packet) {
 
-        DeviceId deviceId = handler().data().deviceId();
-        P4RuntimeController controller = handler().get(P4RuntimeController.class);
-        if (!controller.hasClient(deviceId)) {
-            log.warn("Unable to find client for {}, aborting the sending packet", deviceId);
+        if (!this.setupBehaviour()) {
             return;
         }
 
-        P4RuntimeClient client = controller.getClient(deviceId);
-        PiPipeconfService piPipeconfService = handler().get(PiPipeconfService.class);
-
-        final PiPipeconf pipeconf;
-        if (piPipeconfService.ofDevice(deviceId).isPresent() &&
-                piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).isPresent()) {
-            pipeconf = piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).get();
-        } else {
-            log.warn("Unable to get the pipeconf of {}", deviceId);
-            return;
-        }
-
-        DeviceService deviceService = handler().get(DeviceService.class);
-        Device device = deviceService.getDevice(deviceId);
         final PiPipelineInterpreter interpreter = device.is(PiPipelineInterpreter.class)
                 ? device.as(PiPipelineInterpreter.class) : null;
         if (!device.is(PiPipelineInterpreter.class)) {
-            log.warn("Device {} unable to instantiate interpreter of pipeconf {}", deviceId, pipeconf.id());
+            log.warn("Device {} with pipeconf {} has no interpreter, aborting emit operation", deviceId, pipeconf.id());
             return;
         }
 
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 f709b46..9760ad4 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
@@ -18,12 +18,16 @@
 
 import com.google.common.annotations.Beta;
 import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCellId;
+import org.onosproject.net.pi.runtime.PiCounterId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.runtime.PiTableId;
 
 import java.nio.ByteBuffer;
 import java.util.Collection;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -86,7 +90,7 @@
                                                  PiPipeconf pipeconf);
 
     /**
-     * Dumps all entries currently installed in the given table.
+     * Dumps all entries currently installed in the given table, for the given pipeconf.
      *
      * @param tableId  table identifier
      * @param pipeconf pipeconf currently deployed on the device
@@ -95,7 +99,7 @@
     CompletableFuture<Collection<PiTableEntry>> dumpTable(PiTableId tableId, PiPipeconf pipeconf);
 
     /**
-     * Executes a packet-out operation.
+     * 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
@@ -104,6 +108,27 @@
     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 collection of counter data
+     */
+    CompletableFuture<Collection<PiCounterCellData>> readAllCounterCells(Set<PiCounterId> counterIds,
+                                                                         PiPipeconf pipeconf);
+
+    /**
+     * Returns a collection of counter data corresponding to the given set of counter cell identifiers, for the given
+     * pipeconf.
+     *
+     * @param cellIds set of counter cell identifiers
+     * @param pipeconf   pipeconf
+     * @return collection of counter data
+     */
+    CompletableFuture<Collection<PiCounterCellData>> readCounterCells(Set<PiCounterCellId> cellIds,
+                                                                      PiPipeconf pipeconf);
+
+    /**
      * Shutdown the client by terminating any active RPC such as the stream channel.
      */
     void shutdown();
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
index 3ae9464..87652c3 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
@@ -16,7 +16,9 @@
 
 package org.onosproject.p4runtime.ctl;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
 import com.google.protobuf.ByteString;
 import io.grpc.Context;
 import io.grpc.ManagedChannel;
@@ -26,6 +28,9 @@
 import org.onlab.osgi.DefaultServiceDirectory;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCellId;
+import org.onosproject.net.pi.runtime.PiCounterId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
 import org.onosproject.net.pi.runtime.PiTableEntry;
@@ -34,6 +39,7 @@
 import org.onosproject.p4runtime.api.P4RuntimeEvent;
 import org.slf4j.Logger;
 import p4.P4RuntimeGrpc;
+import p4.P4RuntimeOuterClass.CounterEntry;
 import p4.P4RuntimeOuterClass.Entity;
 import p4.P4RuntimeOuterClass.ForwardingPipelineConfig;
 import p4.P4RuntimeOuterClass.MasterArbitrationUpdate;
@@ -57,6 +63,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
@@ -71,6 +78,7 @@
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType;
 import static org.slf4j.LoggerFactory.getLogger;
+import static p4.P4RuntimeOuterClass.Entity.EntityCase.COUNTER_ENTRY;
 import static p4.P4RuntimeOuterClass.Entity.EntityCase.TABLE_ENTRY;
 import static p4.P4RuntimeOuterClass.PacketOut;
 import static p4.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest.Action.VERIFY_AND_COMMIT;
@@ -175,6 +183,25 @@
         return supplyInContext(() -> doPacketOut(packet, pipeconf), "packetOut");
     }
 
+    @Override
+    public CompletableFuture<Collection<PiCounterCellData>> readCounterCells(Set<PiCounterCellId> cellIds,
+                                                                             PiPipeconf pipeconf) {
+        return supplyInContext(() -> doReadCounterCells(cellIds, pipeconf),
+                               "readCounterCells-" + cellIds.hashCode());
+    }
+
+    @Override
+    public CompletableFuture<Collection<PiCounterCellData>> readAllCounterCells(Set<PiCounterId> counterIds,
+                                                                                PiPipeconf pipeconf) {
+        Set<PiCounterCellId> cellIds = counterIds.stream()
+                // Cell with index 0 means all cells.
+                .map(counterId -> PiCounterCellId.of(counterId, 0))
+                .collect(Collectors.toSet());
+
+        return supplyInContext(() -> doReadCounterCells(cellIds, pipeconf),
+                               "readAllCounterCells-" + cellIds.hashCode());
+    }
+
     /* Blocking method implementations below */
 
     private boolean doInitStreamChannel() {
@@ -404,6 +431,66 @@
         log.warn("Received arbitration update from {} (NOT IMPLEMENTED YET): {}", deviceId, arbitrationMsg);
     }
 
+    private Collection<PiCounterCellData> doReadCounterCells(Collection<PiCounterCellId> cellIds, PiPipeconf pipeconf) {
+
+        // From p4runtime.proto:
+        // For ReadRequest, the scope is defined as follows:
+        // - All counter cells for all meters if counter_id = 0 (default).
+        // - All counter cells for given counter_id if index = 0 (default).
+
+        final ReadRequest.Builder requestBuilder = ReadRequest.newBuilder().setDeviceId(p4DeviceId);
+        final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+        final Map<Integer, PiCounterId> counterIdMap = Maps.newHashMap();
+
+        for (PiCounterCellId cellId : cellIds) {
+            int counterId;
+            try {
+                counterId = browser.counters().getByNameOrAlias(cellId.counterId().id()).getPreamble().getId();
+            } catch (P4InfoBrowser.NotFoundException e) {
+                log.warn("Skipping counter cell {}: {}", cellId, e.getMessage());
+                continue;
+            }
+            requestBuilder
+                    .addEntities(Entity.newBuilder()
+                                         .setCounterEntry(CounterEntry.newBuilder()
+                                                                  .setCounterId(counterId)
+                                                                  .setIndex(cellId.index())
+                                                                  .build()));
+            counterIdMap.put(counterId, cellId.counterId());
+        }
+
+        final Iterator<ReadResponse> responses;
+        try {
+            responses = blockingStub.read(requestBuilder.build());
+        } catch (StatusRuntimeException e) {
+            log.warn("Unable to read counters: {}", e.getMessage());
+            return Collections.emptyList();
+        }
+
+        final Iterable<ReadResponse> responseIterable = () -> responses;
+        final ImmutableList.Builder<PiCounterCellData> piCounterEntryListBuilder = ImmutableList.builder();
+
+        StreamSupport
+                .stream(responseIterable.spliterator(), false)
+                .map(ReadResponse::getEntitiesList)
+                .flatMap(List::stream)
+                .filter(entity -> entity.getEntityCase() == COUNTER_ENTRY)
+                .map(Entity::getCounterEntry)
+                .forEach(counterEntryMsg -> {
+                    if (!counterIdMap.containsKey(counterEntryMsg.getCounterId())) {
+                        log.warn("Unrecognized counter ID '{}', skipping", counterEntryMsg.getCounterId());
+                        return;
+                    }
+                    PiCounterCellId cellId = PiCounterCellId.of(counterIdMap.get(counterEntryMsg.getCounterId()),
+                                                                counterEntryMsg.getIndex());
+                    piCounterEntryListBuilder.add(new PiCounterCellData(cellId,
+                                                                        counterEntryMsg.getData().getPacketCount(),
+                                                                        counterEntryMsg.getData().getByteCount()));
+                });
+
+        return piCounterEntryListBuilder.build();
+    }
+
     /**
      * Returns the internal P4 device ID associated with this client.
      *
diff --git a/tools/dev/bin/onos-setup-p4-dev b/tools/dev/bin/onos-setup-p4-dev
index b4638d4..56eb6d3 100755
--- a/tools/dev/bin/onos-setup-p4-dev
+++ b/tools/dev/bin/onos-setup-p4-dev
@@ -16,7 +16,7 @@
 
 BUILD_DIR=~/p4tools
 BMV2_COMMIT="1683392d8859907b8367cc0188d3d4ed8bea268c"
-PI_COMMIT="b7053f7ad32eb8cddab2ed1f4a7e1e6a30fc5e57"
+PI_COMMIT="6b47641344c2b0bc77e51876517b98eda51eda45"
 P4C_COMMIT="55067fd0e5f9e25fef06e58e49033da3493f796d"
 PROTOBUF_COMMIT="tags/v3.0.2"
 GRPC_COMMIT="tags/v1.3.0"