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/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());
-    }
 }