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()")) {