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/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;