Allow sharing the same gRPC channel between clients

This change introduces a refactoring of the gRPC protocol subsystem that
allows the creation of a gRPC chanel independently of the client, while
allowing multiple clients to share the same channel (e.g. as in Stratum
where we use 3 clients).

Moreover, we refactor the P4RuntimeClient API to support multiple
P4Runtime-internal device ID using the same client. While before the
client was associated to one of such ID.

Finally, we provide an abstract implementation for gRPC-based driver
behaviors, reducing code duplication in P4Runtime, gNMI and gNOI drivers.

Change-Id: I1a46352bbbef1e0d24042f169ae8ba580202944f
diff --git a/drivers/gnmi/BUILD b/drivers/gnmi/BUILD
index efc3f08..f01a9f3 100644
--- a/drivers/gnmi/BUILD
+++ b/drivers/gnmi/BUILD
@@ -7,7 +7,7 @@
     "//protocols/gnmi/stub:onos-protocols-gnmi-stub",
     "//protocols/gnmi/api:onos-protocols-gnmi-api",
     "//protocols/grpc/api:onos-protocols-grpc-api",
-    "//protocols/grpc/proto:onos-protocols-grpc-proto",
+    "//protocols/grpc/utils:onos-protocols-grpc-utils",
 ]
 
 BUNDLES = [
diff --git a/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/AbstractGnmiHandlerBehaviour.java b/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/AbstractGnmiHandlerBehaviour.java
deleted file mode 100644
index 72233fc..0000000
--- a/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/AbstractGnmiHandlerBehaviour.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2018-present Open Networking Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.onosproject.drivers.gnmi;
-
-import com.google.common.base.Strings;
-import org.onosproject.gnmi.api.GnmiClient;
-import org.onosproject.gnmi.api.GnmiClientKey;
-import org.onosproject.gnmi.api.GnmiController;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.config.NetworkConfigService;
-import org.onosproject.net.config.basics.BasicDeviceConfig;
-import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.driver.AbstractHandlerBehaviour;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-
-/**
- * Abstract implementation of a behaviour handler for a gNMI device.
- */
-public class AbstractGnmiHandlerBehaviour extends AbstractHandlerBehaviour {
-
-    protected final Logger log = LoggerFactory.getLogger(getClass());
-    protected DeviceId deviceId;
-    protected DeviceService deviceService;
-    protected GnmiClient client;
-
-    protected boolean setupBehaviour(String opName) {
-        deviceId = handler().data().deviceId();
-        deviceService = handler().get(DeviceService.class);
-        client = getClientByKey();
-        if (client == null) {
-            log.warn("Missing client for {}, aborting {}", deviceId, opName);
-            return false;
-        }
-
-        return true;
-    }
-
-    GnmiClient getClientByKey() {
-        final GnmiClientKey clientKey = clientKey();
-        if (clientKey == null) {
-            return null;
-        }
-        return handler().get(GnmiController.class).getClient(clientKey);
-    }
-
-    protected GnmiClientKey clientKey() {
-        deviceId = handler().data().deviceId();
-
-        final BasicDeviceConfig cfg = handler().get(NetworkConfigService.class)
-                .getConfig(deviceId, BasicDeviceConfig.class);
-        if (cfg == null || Strings.isNullOrEmpty(cfg.managementAddress())) {
-            log.error("Missing or invalid config for {}, cannot derive " +
-                              "gNMI server endpoints", deviceId);
-            return null;
-        }
-
-        try {
-            return new GnmiClientKey(
-                    deviceId, new URI(cfg.managementAddress()));
-        } catch (URISyntaxException e) {
-            log.error("Management address of {} is not a valid URI: {}",
-                      deviceId, cfg.managementAddress());
-            return null;
-        }
-    }
-}
diff --git a/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/GnmiHandshaker.java b/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/GnmiHandshaker.java
index a6d0c04..82fc40c 100644
--- a/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/GnmiHandshaker.java
+++ b/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/GnmiHandshaker.java
@@ -17,33 +17,23 @@
 package org.onosproject.drivers.gnmi;
 
 import org.onosproject.gnmi.api.GnmiClient;
-import org.onosproject.gnmi.api.GnmiClientKey;
 import org.onosproject.gnmi.api.GnmiController;
+import org.onosproject.grpc.utils.AbstractGrpcHandshaker;
 import org.onosproject.net.MastershipRole;
 import org.onosproject.net.device.DeviceHandshaker;
 
 import java.util.concurrent.CompletableFuture;
 
-import static java.util.concurrent.CompletableFuture.completedFuture;
-
 /**
  * Implementation of DeviceHandshaker for gNMI.
  */
-public class GnmiHandshaker extends AbstractGnmiHandlerBehaviour implements DeviceHandshaker {
+public class GnmiHandshaker
+        extends AbstractGrpcHandshaker<GnmiClient, GnmiController>
+        implements DeviceHandshaker {
 
-    @Override
-    public boolean isReachable() {
-        final GnmiClient client = getClientByKey();
-        return client != null && client.isServerReachable();
-    }
 
-    @Override
-    public CompletableFuture<Boolean> probeReachability() {
-        final GnmiClient client = getClientByKey();
-        if (client == null) {
-            return completedFuture(false);
-        }
-        return client.probeService();
+    public GnmiHandshaker() {
+        super(GnmiController.class);
     }
 
     @Override
@@ -65,33 +55,4 @@
     public MastershipRole getRole() {
         throw new UnsupportedOperationException("Mastership operation not supported");
     }
-
-    @Override
-    public CompletableFuture<Boolean> connect() {
-        return CompletableFuture.supplyAsync(this::createClient);
-    }
-
-    private boolean createClient() {
-        GnmiClientKey clientKey = clientKey();
-        if (clientKey == null) {
-            return false;
-        }
-        if (!handler().get(GnmiController.class).createClient(clientKey)) {
-            log.warn("Unable to create client for {}",
-                     handler().data().deviceId());
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public boolean isConnected() {
-        return getClientByKey() != null;
-    }
-
-    @Override
-    public void disconnect() {
-        handler().get(GnmiController.class)
-                .removeClient(handler().data().deviceId());
-    }
 }
diff --git a/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiDeviceDescriptionDiscovery.java b/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiDeviceDescriptionDiscovery.java
index a85cdfb..210a237 100644
--- a/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiDeviceDescriptionDiscovery.java
+++ b/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiDeviceDescriptionDiscovery.java
@@ -23,6 +23,9 @@
 import gnmi.Gnmi.GetRequest;
 import gnmi.Gnmi.GetResponse;
 import org.onlab.packet.ChassisId;
+import org.onosproject.gnmi.api.GnmiClient;
+import org.onosproject.gnmi.api.GnmiController;
+import org.onosproject.grpc.utils.AbstractGrpcHandlerBehaviour;
 import org.onosproject.net.AnnotationKeys;
 import org.onosproject.net.DefaultAnnotations;
 import org.onosproject.net.Device;
@@ -48,7 +51,7 @@
  * supports the gNMI protocol and Openconfig models.
  */
 public class OpenConfigGnmiDeviceDescriptionDiscovery
-        extends AbstractGnmiHandlerBehaviour
+        extends AbstractGrpcHandlerBehaviour<GnmiClient, GnmiController>
         implements DeviceDescriptionDiscovery {
 
     private static final Logger log = LoggerFactory
@@ -58,6 +61,10 @@
 
     private static final String UNKNOWN = "unknown";
 
+    public OpenConfigGnmiDeviceDescriptionDiscovery() {
+        super(GnmiController.class);
+    }
+
     @Override
     public DeviceDescription discoverDeviceDetails() {
         return new DefaultDeviceDescription(
diff --git a/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiPortAdminBehaviour.java b/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiPortAdminBehaviour.java
index 4f050b0..666d5b1 100644
--- a/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiPortAdminBehaviour.java
+++ b/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiPortAdminBehaviour.java
@@ -18,6 +18,8 @@
 
 import gnmi.Gnmi;
 import org.onosproject.gnmi.api.GnmiClient;
+import org.onosproject.gnmi.api.GnmiController;
+import org.onosproject.grpc.utils.AbstractGrpcHandlerBehaviour;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.behaviour.PortAdmin;
 
@@ -29,11 +31,18 @@
  * Implementation of PortAdmin for gNMI devices with OpenConfig support.
  */
 public class OpenConfigGnmiPortAdminBehaviour
-        extends AbstractGnmiHandlerBehaviour
+        extends AbstractGrpcHandlerBehaviour<GnmiClient, GnmiController>
         implements PortAdmin {
 
+    public OpenConfigGnmiPortAdminBehaviour() {
+        super(GnmiController.class);
+    }
+
     @Override
     public CompletableFuture<Boolean> enable(PortNumber number) {
+        if (!setupBehaviour("enable()")) {
+            return completedFuture(false);
+        }
         doEnable(number, true);
         // Always returning true is OK assuming this is used only by the
         // GeneralDeviceProvider, which ignores the return value and instead
@@ -43,6 +52,9 @@
 
     @Override
     public CompletableFuture<Boolean> disable(PortNumber number) {
+        if (!setupBehaviour("disable()")) {
+            return completedFuture(false);
+        }
         doEnable(number, false);
         return completedFuture(true);
     }
@@ -58,11 +70,6 @@
                      portNumber, deviceId);
             return;
         }
-        final GnmiClient client = getClientByKey();
-        if (client == null) {
-            log.warn("Cannot update ports on {}, missing gNMI client", deviceId);
-            return;
-        }
         final Gnmi.Path path = Gnmi.Path.newBuilder()
                 .addElem(Gnmi.PathElem.newBuilder().setName("interfaces").build())
                 .addElem(Gnmi.PathElem.newBuilder().setName("interface")
diff --git a/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiPortStatisticsDiscovery.java b/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiPortStatisticsDiscovery.java
index fa7231f..6e361ff 100644
--- a/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiPortStatisticsDiscovery.java
+++ b/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/OpenConfigGnmiPortStatisticsDiscovery.java
@@ -23,6 +23,9 @@
 import gnmi.Gnmi.GetResponse;
 import gnmi.Gnmi.Path;
 import org.apache.commons.lang3.tuple.Pair;
+import org.onosproject.gnmi.api.GnmiClient;
+import org.onosproject.gnmi.api.GnmiController;
+import org.onosproject.grpc.utils.AbstractGrpcHandlerBehaviour;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
@@ -41,13 +44,18 @@
 /**
  * Behaviour to get port statistics from device via gNMI.
  */
-public class OpenConfigGnmiPortStatisticsDiscovery extends AbstractGnmiHandlerBehaviour
+public class OpenConfigGnmiPortStatisticsDiscovery
+        extends AbstractGrpcHandlerBehaviour<GnmiClient, GnmiController>
         implements PortStatisticsDiscovery {
 
     private static final Map<Pair<DeviceId, PortNumber>, Long> PORT_START_TIMES =
             Maps.newConcurrentMap();
     private static final String LAST_CHANGE = "last-changed";
 
+    public OpenConfigGnmiPortStatisticsDiscovery() {
+        super(GnmiController.class);
+    }
+
     @Override
     public Collection<PortStatistics> discoverPortStatistics() {
         if (!setupBehaviour("discoverPortStatistics()")) {
diff --git a/drivers/gnoi/BUILD b/drivers/gnoi/BUILD
index 3a710a8..b5355a9 100644
--- a/drivers/gnoi/BUILD
+++ b/drivers/gnoi/BUILD
@@ -6,6 +6,7 @@
     "//protocols/gnoi/stub:onos-protocols-gnoi-stub",
     "//protocols/gnoi/api:onos-protocols-gnoi-api",
     "//protocols/grpc/api:onos-protocols-grpc-api",
+    "//protocols/grpc/utils:onos-protocols-grpc-utils",
 ]
 
 BUNDLES = [
diff --git a/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/AbstractGnoiHandlerBehaviour.java b/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/AbstractGnoiHandlerBehaviour.java
deleted file mode 100644
index 84043a8..0000000
--- a/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/AbstractGnoiHandlerBehaviour.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2019-present Open Networking Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.onosproject.drivers.gnoi;
-
-import com.google.common.base.Strings;
-import org.onosproject.gnoi.api.GnoiClient;
-import org.onosproject.gnoi.api.GnoiClientKey;
-import org.onosproject.gnoi.api.GnoiController;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.config.NetworkConfigService;
-import org.onosproject.net.config.basics.BasicDeviceConfig;
-import org.onosproject.net.driver.AbstractHandlerBehaviour;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-
-/**
- * Abstract implementation of a behaviour handler for a gNOI device.
- */
-public class AbstractGnoiHandlerBehaviour extends AbstractHandlerBehaviour {
-
-    protected final Logger log = LoggerFactory.getLogger(getClass());
-    protected DeviceId deviceId;
-    protected GnoiClient client;
-
-    protected boolean setupBehaviour(String opName) {
-        deviceId = handler().data().deviceId();
-        client = getClientByKey();
-
-        if (client == null) {
-            log.warn("Missing client for {}, aborting {}", deviceId, opName);
-            return false;
-        }
-
-        return true;
-    }
-
-    GnoiClient getClientByKey() {
-        final GnoiClientKey clientKey = clientKey();
-        if (clientKey == null) {
-            return null;
-        }
-        return handler().get(GnoiController.class).getClient(clientKey);
-    }
-
-    protected GnoiClientKey clientKey() {
-        deviceId = handler().data().deviceId();
-
-        final BasicDeviceConfig cfg = handler().get(NetworkConfigService.class)
-                .getConfig(deviceId, BasicDeviceConfig.class);
-        if (cfg == null || Strings.isNullOrEmpty(cfg.managementAddress())) {
-            log.error("Missing or invalid config for {}, cannot derive " +
-                    "gNOI server endpoints", deviceId);
-            return null;
-        }
-
-        try {
-            return new GnoiClientKey(
-                    deviceId, new URI(cfg.managementAddress()));
-        } catch (URISyntaxException e) {
-            log.error("Management address of {} is not a valid URI: {}",
-                    deviceId, cfg.managementAddress());
-            return null;
-        }
-    }
-}
diff --git a/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiBasicSystemOperationsImpl.java b/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiBasicSystemOperationsImpl.java
index b979546..7876886 100644
--- a/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiBasicSystemOperationsImpl.java
+++ b/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiBasicSystemOperationsImpl.java
@@ -19,9 +19,10 @@
 import gnoi.system.SystemOuterClass.RebootMethod;
 import gnoi.system.SystemOuterClass.RebootRequest;
 import gnoi.system.SystemOuterClass.RebootResponse;
+import org.onosproject.gnoi.api.GnoiClient;
+import org.onosproject.gnoi.api.GnoiController;
+import org.onosproject.grpc.utils.AbstractGrpcHandlerBehaviour;
 import org.onosproject.net.behaviour.BasicSystemOperations;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.concurrent.CompletableFuture;
 
@@ -30,10 +31,12 @@
  * devices.
  */
 public class GnoiBasicSystemOperationsImpl
-        extends AbstractGnoiHandlerBehaviour implements BasicSystemOperations {
+        extends AbstractGrpcHandlerBehaviour<GnoiClient, GnoiController>
+        implements BasicSystemOperations {
 
-    private static final Logger log = LoggerFactory
-            .getLogger(GnoiBasicSystemOperationsImpl.class);
+    public GnoiBasicSystemOperationsImpl() {
+        super(GnoiController.class);
+    }
 
     @Override
     public CompletableFuture<Boolean> reboot() {
diff --git a/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiDeviceDescriptionDiscovery.java b/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiDeviceDescriptionDiscovery.java
index 3c308d6..2010c53 100644
--- a/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiDeviceDescriptionDiscovery.java
+++ b/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiDeviceDescriptionDiscovery.java
@@ -24,6 +24,7 @@
 import org.onosproject.net.device.DeviceDescription;
 import org.onosproject.net.device.DeviceDescriptionDiscovery;
 import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
 
 import java.util.Collections;
 import java.util.List;
@@ -32,7 +33,8 @@
  * Implementation of DeviceDescriptionDiscovery for gNOI devices.
  */
 public class GnoiDeviceDescriptionDiscovery
-        extends AbstractGnoiHandlerBehaviour implements DeviceDescriptionDiscovery {
+        extends AbstractHandlerBehaviour
+        implements DeviceDescriptionDiscovery {
 
     private static final String UNKNOWN = "unknown";
 
diff --git a/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiHandshaker.java b/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiHandshaker.java
index 36a4971..1668bcc 100644
--- a/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiHandshaker.java
+++ b/drivers/gnoi/src/main/java/org/onosproject/drivers/gnoi/GnoiHandshaker.java
@@ -17,33 +17,22 @@
 package org.onosproject.drivers.gnoi;
 
 import org.onosproject.gnoi.api.GnoiClient;
-import org.onosproject.gnoi.api.GnoiClientKey;
 import org.onosproject.gnoi.api.GnoiController;
+import org.onosproject.grpc.utils.AbstractGrpcHandshaker;
 import org.onosproject.net.MastershipRole;
 import org.onosproject.net.device.DeviceHandshaker;
 
 import java.util.concurrent.CompletableFuture;
 
-import static java.util.concurrent.CompletableFuture.completedFuture;
-
 /**
  * Implementation of DeviceHandshaker for gNOI.
  */
-public class GnoiHandshaker extends AbstractGnoiHandlerBehaviour implements DeviceHandshaker {
+public class GnoiHandshaker
+        extends AbstractGrpcHandshaker<GnoiClient, GnoiController>
+        implements DeviceHandshaker {
 
-    @Override
-    public boolean isReachable() {
-        final GnoiClient client = getClientByKey();
-        return client != null && client.isServerReachable();
-    }
-
-    @Override
-    public CompletableFuture<Boolean> probeReachability() {
-        final GnoiClient client = getClientByKey();
-        if (client == null) {
-            return completedFuture(false);
-        }
-        return client.probeService();
+    public GnoiHandshaker() {
+        super(GnoiController.class);
     }
 
     @Override
@@ -65,33 +54,4 @@
     public MastershipRole getRole() {
         throw new UnsupportedOperationException("Mastership operation not supported");
     }
-
-    @Override
-    public CompletableFuture<Boolean> connect() {
-        return CompletableFuture.supplyAsync(this::createClient);
-    }
-
-    private boolean createClient() {
-        GnoiClientKey clientKey = clientKey();
-        if (clientKey == null) {
-            return false;
-        }
-        if (!handler().get(GnoiController.class).createClient(clientKey)) {
-            log.warn("Unable to create client for {}",
-                    handler().data().deviceId());
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public boolean isConnected() {
-        return getClientByKey() != null;
-    }
-
-    @Override
-    public void disconnect() {
-        handler().get(GnoiController.class)
-                .removeClient(handler().data().deviceId());
-    }
 }
diff --git a/drivers/p4runtime/BUILD b/drivers/p4runtime/BUILD
index 7f917cd..f97dbd7 100644
--- a/drivers/p4runtime/BUILD
+++ b/drivers/p4runtime/BUILD
@@ -1,5 +1,7 @@
 COMPILE_DEPS = CORE_DEPS + KRYO + [
     "//core/store/serializers:onos-core-serializers",
+    "//protocols/grpc/api:onos-protocols-grpc-api",
+    "//protocols/grpc/utils:onos-protocols-grpc-utils",
     "//protocols/p4runtime/api:onos-protocols-p4runtime-api",
     "@io_grpc_grpc_java//core",
 ]
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimeHandlerBehaviour.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimeHandlerBehaviour.java
index c71907b..7bbc00b 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimeHandlerBehaviour.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimeHandlerBehaviour.java
@@ -16,40 +16,31 @@
 
 package org.onosproject.drivers.p4runtime;
 
-import com.google.common.base.Strings;
-import org.onosproject.net.Device;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.config.NetworkConfigService;
-import org.onosproject.net.config.basics.BasicDeviceConfig;
-import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.grpc.utils.AbstractGrpcHandlerBehaviour;
 import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.service.PiPipeconfService;
 import org.onosproject.net.pi.service.PiTranslationService;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
-import org.onosproject.p4runtime.api.P4RuntimeClientKey;
 import org.onosproject.p4runtime.api.P4RuntimeController;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.URI;
-import java.net.URISyntaxException;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.drivers.p4runtime.P4RuntimeDriverUtils.extractP4DeviceId;
 
 /**
  * Abstract implementation of a behaviour handler for a P4Runtime device.
  */
-public class AbstractP4RuntimeHandlerBehaviour extends AbstractHandlerBehaviour {
-
-    protected final Logger log = LoggerFactory.getLogger(getClass());
+public abstract class AbstractP4RuntimeHandlerBehaviour
+        extends AbstractGrpcHandlerBehaviour<P4RuntimeClient, P4RuntimeController> {
 
     // Initialized by setupBehaviour()
-    protected DeviceId deviceId;
+    protected Long p4DeviceId;
     protected PiPipeconf pipeconf;
-    protected P4RuntimeClient client;
-    protected PiTranslationService translationService;
+    PiTranslationService translationService;
+
+
+    AbstractP4RuntimeHandlerBehaviour() {
+        super(P4RuntimeController.class);
+    }
 
     /**
      * Initializes this behaviour attributes. Returns true if the operation was
@@ -59,20 +50,25 @@
      * @return true if successful, false otherwise
      */
     protected boolean setupBehaviour(String opName) {
-        deviceId = handler().data().deviceId();
-
-        client = getClientByKey();
-        if (client == null) {
-            log.warn("Missing client for {}, aborting {}", deviceId, opName);
+        if (!super.setupBehaviour(opName)) {
             return false;
         }
 
-        PiPipeconfService piPipeconfService = handler().get(PiPipeconfService.class);
-        if (!piPipeconfService.getPipeconf(deviceId).isPresent()) {
+        p4DeviceId = extractP4DeviceId(mgmtUriFromNetcfg());
+        if (p4DeviceId == null) {
+            log.warn("Unable to obtain P4Runtime-internal device_id from " +
+                             "config of {}, cannot perform {}",
+                     deviceId, opName);
+            return false;
+        }
+
+        final PiPipeconfService pipeconfService = handler().get(
+                PiPipeconfService.class);
+        if (!pipeconfService.getPipeconf(deviceId).isPresent()) {
             log.warn("Missing pipeconf for {}, cannot perform {}", deviceId, opName);
             return false;
         }
-        pipeconf = piPipeconfService.getPipeconf(deviceId).get();
+        pipeconf = pipeconfService.getPipeconf(deviceId).get();
 
         translationService = handler().get(PiTranslationService.class);
 
@@ -80,61 +76,6 @@
     }
 
     /**
-     * Returns an instance of the interpreter implementation for this device,
-     * null if an interpreter cannot be retrieved.
-     *
-     * @return interpreter or null
-     */
-    PiPipelineInterpreter getInterpreter() {
-        final Device device = handler().get(DeviceService.class).getDevice(deviceId);
-        if (device == null) {
-            log.warn("Unable to find device {}, cannot get interpreter", deviceId);
-            return null;
-        }
-        if (!device.is(PiPipelineInterpreter.class)) {
-            log.warn("Unable to get interpreter for {}, missing behaviour",
-                     deviceId);
-            return null;
-        }
-        return device.as(PiPipelineInterpreter.class);
-    }
-
-    /**
-     * Returns a P4Runtime client previsouly created for this device, null if
-     * such client does not exist.
-     *
-     * @return client or null
-     */
-    P4RuntimeClient getClientByKey() {
-        final P4RuntimeClientKey clientKey = clientKey();
-        if (clientKey == null) {
-            return null;
-        }
-        return handler().get(P4RuntimeController.class).getClient(clientKey);
-    }
-
-    protected P4RuntimeClientKey clientKey() {
-        deviceId = handler().data().deviceId();
-
-        final BasicDeviceConfig cfg = handler().get(NetworkConfigService.class)
-                .getConfig(deviceId, BasicDeviceConfig.class);
-        if (cfg == null || Strings.isNullOrEmpty(cfg.managementAddress())) {
-            log.error("Missing or invalid config for {}, cannot derive " +
-                              "P4Runtime server endpoints", deviceId);
-            return null;
-        }
-
-        try {
-            return new P4RuntimeClientKey(
-                    deviceId, new URI(cfg.managementAddress()));
-        } catch (URISyntaxException e) {
-            log.error("Management address of {} is not a valid URI: {}",
-                      deviceId, cfg.managementAddress());
-            return null;
-        }
-    }
-
-    /**
      * Returns the value of the given driver property, if present, otherwise
      * returns the given default value.
      *
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java
index 916a945..9b700ca 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java
@@ -16,11 +16,8 @@
 
 package org.onosproject.drivers.p4runtime;
 
-import org.onosproject.net.DeviceId;
 import org.onosproject.net.behaviour.PiPipelineProgrammable;
 import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.p4runtime.api.P4RuntimeClient;
-import org.onosproject.p4runtime.api.P4RuntimeController;
 import org.slf4j.Logger;
 
 import java.nio.ByteBuffer;
@@ -49,42 +46,32 @@
 
     @Override
     public CompletableFuture<Boolean> setPipeconf(PiPipeconf pipeconf) {
-        DeviceId deviceId = handler().data().deviceId();
-        P4RuntimeController controller = handler().get(P4RuntimeController.class);
-
-        P4RuntimeClient client = controller.getClient(deviceId);
-        if (client == null) {
-            log.warn("Unable to find client for {}, aborting pipeconf deploy", deviceId);
+        if (!setupBehaviour("setPipeconf()")) {
             return completedFuture(false);
         }
 
-        ByteBuffer deviceDataBuffer = createDeviceDataBuffer(pipeconf);
+        final ByteBuffer deviceDataBuffer = createDeviceDataBuffer(pipeconf);
         if (deviceDataBuffer == null) {
             // Hopefully the child class logged the problem.
             return completedFuture(false);
         }
 
-        return client.setPipelineConfig(pipeconf, deviceDataBuffer);
+        return client.setPipelineConfig(p4DeviceId, pipeconf, deviceDataBuffer);
     }
 
     @Override
     public CompletableFuture<Boolean> isPipeconfSet(PiPipeconf pipeconf) {
-        DeviceId deviceId = handler().data().deviceId();
-        P4RuntimeController controller = handler().get(P4RuntimeController.class);
-
-        P4RuntimeClient client = controller.getClient(deviceId);
-        if (client == null) {
-            log.warn("Unable to find client for {}, cannot check if pipeconf is set", deviceId);
+        if (!setupBehaviour("isPipeconfSet()")) {
             return completedFuture(false);
         }
 
-        ByteBuffer deviceDataBuffer = createDeviceDataBuffer(pipeconf);
+        final ByteBuffer deviceDataBuffer = createDeviceDataBuffer(pipeconf);
         if (deviceDataBuffer == null) {
             // Hopefully the child class logged the problem.
             return completedFuture(false);
         }
 
-        return client.isPipelineConfigSet(pipeconf, deviceDataBuffer);
+        return client.isPipelineConfigSet(p4DeviceId, pipeconf, deviceDataBuffer);
     }
 
     @Override
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeActionGroupProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeActionGroupProgrammable.java
index d14b917..612394f 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeActionGroupProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeActionGroupProgrammable.java
@@ -131,7 +131,8 @@
         }
 
         // Dump groups and members from device for all action profiles.
-        final P4RuntimeReadClient.ReadRequest request = client.read(pipeconf);
+        final P4RuntimeReadClient.ReadRequest request = client.read(
+                p4DeviceId, pipeconf);
         pipeconf.pipelineModel().actionProfiles()
                 .stream().map(PiActionProfileModel::id)
                 .forEach(id -> request.actionProfileGroups(id)
@@ -184,7 +185,7 @@
             log.warn("Cleaning up {} action profile groups and " +
                              "{} members on {}...",
                      groupHandlesToRemove.size(), memberHandlesToRemove.size(), deviceId);
-            client.write(pipeconf)
+            client.write(p4DeviceId, pipeconf)
                     .delete(groupHandlesToRemove)
                     .delete(memberHandlesToRemove)
                     .submit().whenComplete((r, ex) -> {
@@ -244,7 +245,8 @@
         // found on the device.
         if (!validateGroupMembers(piGroupFromStore, membersOnDevice)) {
             log.warn("Group on device {} refers to members that are different " +
-                             "than those found in translation store: {}", handle);
+                             "than those found in translation store: {}",
+                     deviceId, handle);
             return null;
         }
         if (mirrorEntry == null) {
@@ -308,7 +310,7 @@
         if (members == null) {
             return;
         }
-        final WriteRequest request = client.write(pipeconf);
+        final WriteRequest request = client.write(p4DeviceId, pipeconf);
         WRITE_LOCKS.get(deviceId).lock();
         try {
             if (operation == Operation.APPLY) {
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeDeviceDescriptionDiscovery.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeDeviceDescriptionDiscovery.java
index 337ce6a..4b3dba9 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeDeviceDescriptionDiscovery.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeDeviceDescriptionDiscovery.java
@@ -24,21 +24,25 @@
 import org.onosproject.net.device.DeviceDescription;
 import org.onosproject.net.device.DeviceDescriptionDiscovery;
 import org.onosproject.net.device.PortDescription;
-import org.onosproject.net.driver.AbstractHandlerBehaviour;
 
 import java.util.Collections;
 import java.util.List;
 
+import static org.onosproject.drivers.p4runtime.P4RuntimeDriverUtils.extractP4DeviceId;
+
 /**
  * Implementation of DeviceDescriptionDiscovery for P4Runtime devices.
  */
 public class P4RuntimeDeviceDescriptionDiscovery
-        extends AbstractHandlerBehaviour implements DeviceDescriptionDiscovery {
+        extends AbstractP4RuntimeHandlerBehaviour
+        implements DeviceDescriptionDiscovery {
 
     private static final String UNKNOWN = "unknown";
+    private static final String P4_DEVICE_ID = "p4DeviceId";
 
     @Override
     public DeviceDescription discoverDeviceDetails() {
+        final Long p4DeviceId = extractP4DeviceId(mgmtUriFromNetcfg());
         return new DefaultDeviceDescription(
                 data().deviceId().uri(),
                 Device.Type.SWITCH,
@@ -49,6 +53,8 @@
                 new ChassisId(),
                 false,
                 DefaultAnnotations.builder()
+                        .set(P4_DEVICE_ID, p4DeviceId == null
+                                ? UNKNOWN : String.valueOf(p4DeviceId))
                         .set(AnnotationKeys.PROTOCOL, "P4Runtime")
                         .build());
     }
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeDriverUtils.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeDriverUtils.java
new file mode 100644
index 0000000..73eed40
--- /dev/null
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeDriverUtils.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.drivers.p4runtime;
+
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+
+/**
+ * Utility class for the P4Runtime driver.
+ */
+final class P4RuntimeDriverUtils {
+
+    private static final String DEVICE_ID_PARAM = "device_id=";
+
+    private static final Logger log = LoggerFactory.getLogger(P4RuntimeDriverUtils.class);
+
+    private P4RuntimeDriverUtils() {
+        // Hide constructor.
+    }
+
+    /**
+     * Returns an instance of the interpreter implementation for this device,
+     * null if an interpreter cannot be retrieved.
+     *
+     * @param handler driver handler
+     * @return interpreter or null
+     */
+    static PiPipelineInterpreter getInterpreter(DriverHandler handler) {
+        final DeviceId deviceId = handler.data().deviceId();
+        final Device device = handler.get(DeviceService.class).getDevice(deviceId);
+        if (device == null) {
+            log.warn("Unable to find device {}, cannot get interpreter", deviceId);
+            return null;
+        }
+        if (!device.is(PiPipelineInterpreter.class)) {
+            log.warn("Unable to get interpreter for {}, missing behaviour",
+                     deviceId);
+            return null;
+        }
+        return device.as(PiPipelineInterpreter.class);
+    }
+
+    static Long extractP4DeviceId(URI uri) {
+        if (uri == null) {
+            return null;
+        }
+        String[] segments = uri.getRawQuery().split("&");
+        try {
+            for (String s : segments) {
+                if (s.startsWith(DEVICE_ID_PARAM)) {
+                    return Long.parseUnsignedLong(
+                            URLDecoder.decode(
+                                    s.substring(DEVICE_ID_PARAM.length()), "utf-8"));
+                }
+            }
+        } catch (UnsupportedEncodingException e) {
+            log.error("Unable to decode P4Runtime-internal device_id from URI {}: {}",
+                      uri, e.toString());
+        } catch (NumberFormatException e) {
+            log.error("Invalid P4Runtime-internal device_id in URI {}: {}",
+                      uri, e.toString());
+        }
+        log.error("Missing P4Runtime-internal device_id in URI {}", uri);
+        return null;
+    }
+}
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 d55dd71..7432d2d 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
@@ -58,6 +58,7 @@
 import java.util.concurrent.locks.Lock;
 import java.util.stream.Collectors;
 
+import static org.onosproject.drivers.p4runtime.P4RuntimeDriverUtils.getInterpreter;
 import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
 import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
 import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
@@ -175,7 +176,7 @@
             log.warn("Found {} inconsistent table entries on {}, removing them...",
                      inconsistentEntries.size(), deviceId);
             // Submit delete request and update mirror when done.
-            client.write(pipeconf)
+            client.write(p4DeviceId, pipeconf)
                     .entities(inconsistentEntries, DELETE)
                     .submit().whenComplete((response, ex) -> {
                 if (ex != null) {
@@ -193,7 +194,8 @@
     }
 
     private Collection<PiTableEntry> getAllTableEntriesFromDevice() {
-        final P4RuntimeReadClient.ReadRequest request = client.read(pipeconf);
+        final P4RuntimeReadClient.ReadRequest request = client.read(
+                p4DeviceId, pipeconf);
         // Read entries from all non-constant tables, including default ones.
         pipelineModel.tables().stream()
                 .filter(t -> !t.isConstantTable())
@@ -274,7 +276,7 @@
             return Collections.emptyList();
         }
         // Created batched write request.
-        final WriteRequest request = client.write(pipeconf);
+        final WriteRequest request = client.write(p4DeviceId, pipeconf);
         // For each rule, translate to PI and append to write request.
         final Map<PiHandle, FlowRule> handleToRuleMap = Maps.newHashMap();
         final List<FlowRule> skippedRules = Lists.newArrayList();
@@ -433,7 +435,7 @@
     }
 
     private PiTableEntry getOriginalDefaultEntry(PiTableId tableId) {
-        final PiPipelineInterpreter interpreter = getInterpreter();
+        final PiPipelineInterpreter interpreter = getInterpreter(handler());
         if (interpreter == null) {
             log.warn("Missing interpreter for {}, cannot get default action",
                      deviceId);
@@ -483,7 +485,7 @@
                     .map(id -> PiCounterCellHandle.of(deviceId, id))
                     .collect(Collectors.toSet());
             // FIXME: We might be sending a very large read request...
-            return client.read(pipeconf)
+            return client.read(p4DeviceId, pipeconf)
                     .handles(cellHandles)
                     .submitSync()
                     .all(PiCounterCell.class).stream()
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 64611fe..e689298 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
@@ -17,24 +17,27 @@
 package org.onosproject.drivers.p4runtime;
 
 import org.onosproject.cluster.ClusterService;
+import org.onosproject.grpc.utils.AbstractGrpcHandshaker;
 import org.onosproject.net.MastershipRole;
 import org.onosproject.net.device.DeviceAgentListener;
 import org.onosproject.net.device.DeviceHandshaker;
 import org.onosproject.net.pi.service.PiPipeconfWatchdogService;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
-import org.onosproject.p4runtime.api.P4RuntimeClientKey;
 import org.onosproject.p4runtime.api.P4RuntimeController;
 
 import java.math.BigInteger;
 import java.util.concurrent.CompletableFuture;
 
 import static java.util.concurrent.CompletableFuture.completedFuture;
+import static org.onosproject.drivers.p4runtime.P4RuntimeDriverUtils.extractP4DeviceId;
 
 /**
  * Implementation of DeviceHandshaker for P4Runtime.
  */
-public class P4RuntimeHandshaker extends AbstractP4RuntimeHandlerBehaviour implements DeviceHandshaker {
+public class P4RuntimeHandshaker
+        extends AbstractGrpcHandshaker<P4RuntimeClient, P4RuntimeController>
+        implements DeviceHandshaker {
 
     // This is needed to compute an election ID based on mastership term and
     // preference. At the time of writing the practical maximum cluster size is
@@ -44,60 +47,35 @@
     // election IDs (e.g. two nodes seeing different cluster sizes).
     private static final int MAX_CLUSTER_SIZE = 20;
 
-    @Override
-    public CompletableFuture<Boolean> connect() {
-        return CompletableFuture
-                .supplyAsync(this::createClient);
+    private Long p4DeviceId;
+
+    public P4RuntimeHandshaker() {
+        super(P4RuntimeController.class);
     }
 
-    private boolean createClient() {
-        final P4RuntimeClientKey clientKey = clientKey();
-        if (clientKey == null) {
+    @Override
+    protected boolean setupBehaviour(String opName) {
+        if (!super.setupBehaviour(opName)) {
             return false;
         }
-        if (!handler().get(P4RuntimeController.class).createClient(clientKey)) {
-            log.debug("Unable to create client for {}", data().deviceId());
+
+        p4DeviceId = extractP4DeviceId(mgmtUriFromNetcfg());
+        if (p4DeviceId == null) {
+            log.warn("Unable to obtain the P4Runtime-internal device_id from " +
+                             "config of {}, cannot perform {}",
+                     deviceId, opName);
             return false;
         }
         return true;
     }
 
     @Override
-    public boolean isConnected() {
-        // This is based on the client key obtained from the current netcfg. If
-        // a client already exists for this device, but the netcfg with the
-        // server endpoints has changed, this will return false.
-        return getClientByKey() != null;
-    }
-
-    @Override
-    public void disconnect() {
-        // This removes clients associated with this device ID, even if the
-        // netcfg has changed and so the client key for this device.
-        handler().get(P4RuntimeController.class).removeClient(data().deviceId());
-    }
-
-    @Override
-    public boolean isReachable() {
-        final P4RuntimeClient client = getClientByKey();
-        return client != null && client.isServerReachable();
-    }
-
-    @Override
-    public CompletableFuture<Boolean> probeReachability() {
-        final P4RuntimeClient client = getClientByKey();
-        if (client == null) {
-            return completedFuture(false);
-        }
-        return client.probeService();
-    }
-
-    @Override
     public boolean isAvailable() {
         // To be available, we require a session open (for packet in/out) and a
         // pipeline config set.
-        final P4RuntimeClient client = getClientByKey();
-        if (client == null || !client.isServerReachable() || !client.isSessionOpen()) {
+        if (!setupBehaviour("isAvailable()") ||
+                !client.isServerReachable() ||
+                !client.isSessionOpen(p4DeviceId)) {
             return false;
         }
         // Since we cannot probe the device, we rely on what's known by the
@@ -111,11 +89,12 @@
     public CompletableFuture<Boolean> probeAvailability() {
         // To be available, we require a session open (for packet in/out) and a
         // pipeline config set.
-        final P4RuntimeClient client = getClientByKey();
-        if (client == null || !client.isServerReachable() || !client.isSessionOpen()) {
+        if (!setupBehaviour("probeAvailability()") ||
+                !client.isServerReachable() ||
+                !client.isSessionOpen(p4DeviceId)) {
             return completedFuture(false);
         }
-        return client.isAnyPipelineConfigSet();
+        return client.isAnyPipelineConfigSet(p4DeviceId);
     }
 
     @Override
@@ -125,7 +104,7 @@
         }
         if (newRole.equals(MastershipRole.NONE)) {
             log.info("Notified role NONE, closing session...");
-            client.closeSession();
+            client.closeSession(p4DeviceId);
         } else {
             throw new UnsupportedOperationException(
                     "Use preference-based way for setting MASTER or STANDBY roles");
@@ -143,19 +122,21 @@
             throw new IllegalStateException(
                     "Cluster too big! Maz size supported is " + MAX_CLUSTER_SIZE);
         }
-        BigInteger electionId = BigInteger.valueOf(term)
+        final BigInteger electionId = BigInteger.valueOf(term)
                 .multiply(BigInteger.valueOf(MAX_CLUSTER_SIZE))
                 .subtract(BigInteger.valueOf(preference));
-        client.setMastership(preference == 0, electionId);
+        client.setMastership(p4DeviceId, preference == 0, electionId);
     }
 
     @Override
     public MastershipRole getRole() {
-        final P4RuntimeClient client = getClientByKey();
-        if (client == null || !client.isServerReachable() || !client.isSessionOpen()) {
+        if (!setupBehaviour("getRole()") ||
+                !client.isServerReachable() ||
+                !client.isSessionOpen(p4DeviceId)) {
             return MastershipRole.NONE;
         }
-        return client.isMaster() ? MastershipRole.MASTER : MastershipRole.STANDBY;
+        return client.isMaster(p4DeviceId)
+                ? MastershipRole.MASTER : MastershipRole.STANDBY;
     }
 
     @Override
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java
index 73faf45..796bea6 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMeterProgrammable.java
@@ -105,7 +105,7 @@
 
         final PiMeterCellHandle handle = PiMeterCellHandle.of(deviceId, piMeterCellConfig);
         ENTRY_LOCKS.getUnchecked(handle).lock();
-        final boolean result = client.write(pipeconf)
+        final boolean result = client.write(p4DeviceId, pipeconf)
                 .modify(piMeterCellConfig).submitSync().isSuccess();
         if (result) {
             meterMirror.put(handle, piMeterCellConfig);
@@ -129,7 +129,7 @@
             meterIds.add(mode.id());
         }
 
-        piMeterCellConfigs = client.read(pipeconf)
+        piMeterCellConfigs = client.read(p4DeviceId, pipeconf)
                 .meterCells(meterIds).submitSync().all(PiMeterCellConfig.class);
 
         Collection<Meter> meters = piMeterCellConfigs.stream()
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMulticastGroupProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMulticastGroupProgrammable.java
index 80c771e..212ada2 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMulticastGroupProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeMulticastGroupProgrammable.java
@@ -145,7 +145,7 @@
                 return;
             case MODIFY:
                 // Since reading multicast groups is not supported yet on
-                // PI/Stratum, we cannot trust groupOnDevic) as we don't have a
+                // PI/Stratum, we cannot trust groupOnDevice as we don't have a
                 // mechanism to enforce consistency of the mirror with the
                 // device state.
                 // if (driverBoolProperty(CHECK_MIRROR_BEFORE_UPDATE,
@@ -169,7 +169,8 @@
 
     private boolean writeMcGroupOnDevice(
             PiMulticastGroupEntry group, P4RuntimeClient.UpdateType opType) {
-        return client.write(pipeconf).entity(group, opType).submitSync().isSuccess();
+        return client.write(p4DeviceId, pipeconf)
+                .entity(group, opType).submitSync().isSuccess();
     }
 
     private boolean mcGroupApply(PiMulticastGroupEntryHandle handle,
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 9010c51..8a182a7 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
@@ -23,6 +23,8 @@
 
 import java.util.Collection;
 
+import static org.onosproject.drivers.p4runtime.P4RuntimeDriverUtils.getInterpreter;
+
 /**
  * Implementation of PacketProgrammable behaviour for P4Runtime.
  */
@@ -37,7 +39,7 @@
             return;
         }
 
-        final PiPipelineInterpreter interpreter = getInterpreter();
+        final PiPipelineInterpreter interpreter = getInterpreter(handler());
         if (interpreter == null) {
             // Error logged by getInterpreter().
             return;
@@ -47,7 +49,7 @@
             Collection<PiPacketOperation> operations = interpreter.mapOutboundPacket(packet);
             operations.forEach(piPacketOperation -> {
                 log.debug("Doing PiPacketOperation {}", piPacketOperation);
-                client.packetOut(piPacketOperation, pipeconf);
+                client.packetOut(p4DeviceId, piPacketOperation, pipeconf);
             });
         } catch (PiPipelineInterpreter.PiInterpreterException e) {
             log.error("Unable to translate outbound packet for {} with pipeconf {}: {}",
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeTableStatisticsDiscovery.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeTableStatisticsDiscovery.java
index 1b390ef..8a32232 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeTableStatisticsDiscovery.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeTableStatisticsDiscovery.java
@@ -36,6 +36,7 @@
 import java.util.Map;
 
 import static com.google.common.collect.Lists.newArrayList;
+import static org.onosproject.drivers.p4runtime.P4RuntimeDriverUtils.getInterpreter;
 
 /**
  * Implementation of behaviour TableStatisticsDiscovery for P4Runtime.
@@ -49,7 +50,7 @@
             return Collections.emptyList();
         }
         FlowRuleService flowService = handler().get(FlowRuleService.class);
-        PiPipelineInterpreter interpreter = getInterpreter();
+        PiPipelineInterpreter interpreter = getInterpreter(handler());
         PiPipelineModel model = pipeconf.pipelineModel();
         List<TableStatisticsEntry> tableStatsList;
 
diff --git a/drivers/stratum/BUILD b/drivers/stratum/BUILD
index 1d19c3c..a87c97e 100644
--- a/drivers/stratum/BUILD
+++ b/drivers/stratum/BUILD
@@ -4,6 +4,8 @@
     "//drivers/gnmi:onos-drivers-gnmi",
     "//drivers/gnoi:onos-drivers-gnoi",
     "//pipelines/basic:onos-pipelines-basic",
+    "//protocols/grpc/api:onos-protocols-grpc-api",
+    "//protocols/grpc/utils:onos-protocols-grpc-utils",
 ]
 
 osgi_jar(
diff --git a/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumDeviceDescriptionDiscovery.java b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumDeviceDescriptionDiscovery.java
index dbcb193..fdf8173 100644
--- a/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumDeviceDescriptionDiscovery.java
+++ b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumDeviceDescriptionDiscovery.java
@@ -63,6 +63,9 @@
                 // Availability is mandated by P4Runtime.
                 p4Descr.isDefaultAvailable(),
                 DefaultAnnotations.builder()
+                        .putAll(p4Descr.annotations())
+                        .putAll(gnmiDescr.annotations())
+                        .putAll(gnoiDescr.annotations())
                         .set(AnnotationKeys.PROTOCOL, format(
                                 "%s, %s, %s",
                                 p4Descr.annotations().value(AnnotationKeys.PROTOCOL),
diff --git a/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumHandshaker.java b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumHandshaker.java
index d512d3b..ac9a534 100644
--- a/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumHandshaker.java
+++ b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumHandshaker.java
@@ -38,6 +38,23 @@
     }
 
     @Override
+    public boolean connect() {
+        return p4runtime.connect() && gnmi.connect() && gnoi.connect();
+    }
+
+    @Override
+    public boolean hasConnection() {
+        return p4runtime.hasConnection() && gnmi.hasConnection() && gnoi.hasConnection();
+    }
+
+    @Override
+    public void disconnect() {
+        p4runtime.disconnect();
+        gnmi.disconnect();
+        gnoi.disconnect();
+    }
+
+    @Override
     public boolean isReachable() {
         // Reachability is mainly used for mastership contests and it's a
         // prerequisite for availability. We can probably live without gNMI and
@@ -86,23 +103,4 @@
     public void removeDeviceAgentListener(ProviderId providerId) {
         p4runtime.removeDeviceAgentListener(providerId);
     }
-
-    @Override
-    public CompletableFuture<Boolean> connect() {
-        // We should execute connections in parallel.
-        return p4runtime.connect().thenCombine(gnmi.connect(), Boolean::logicalAnd)
-                .thenCombine(gnoi.connect(), Boolean::logicalAnd);
-    }
-
-    @Override
-    public boolean isConnected() {
-        return p4runtime.isConnected() && gnmi.isConnected() && gnoi.isConnected();
-    }
-
-    @Override
-    public void disconnect() {
-        p4runtime.disconnect();
-        gnmi.disconnect();
-        gnoi.disconnect();
-    }
 }