Cherry pick gNMI and Stratum related changes to this branch

Cherry picked commits:
20211 Update gNMI version and build script
20247 [ONOS-7829] Implement AbstractGrpcClient and AbstractGrpcClientControl
20233 [ONOS-7141][ONOS-7142] Add GnmiClient and GnmiController
20234 Refactor OpenConfig gNMI device description descovery
20260 [ONOS-7831] Implement GnmiHandshaker
20270 Add Stratum driver

Change-Id: I81ad8bce45251af5909cfcac0edbcfd11c8ebf1d
diff --git a/drivers/gnmi/BUILD b/drivers/gnmi/BUILD
index 4a4c100..efc3f08 100644
--- a/drivers/gnmi/BUILD
+++ b/drivers/gnmi/BUILD
@@ -28,7 +28,6 @@
     included_bundles = BUNDLES,
     required_apps = [
         "org.onosproject.generaldeviceprovider",
-        "org.onosproject.protocols.grpc",
         "org.onosproject.protocols.gnmi",
     ],
     title = "gNMI Drivers",
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
index e6d8b16..3bfca46 100644
--- a/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/AbstractGnmiHandlerBehaviour.java
+++ b/drivers/gnmi/src/main/java/org/onosproject/drivers/gnmi/AbstractGnmiHandlerBehaviour.java
@@ -41,9 +41,8 @@
     private static final String DEVICE_REQ_TIMEOUT = "deviceRequestTimeout";
     private static final int DEFAULT_DEVICE_REQ_TIMEOUT = 60;
 
-    public static final String GNMI_SERVER_ADDR_KEY = "gnmi_ip";
-    public static final String GNMI_SERVER_PORT_KEY = "gnmi_port";
-    private static final String GNMI_SERVICE_NAME = "gnmi";
+    private static final String GNMI_SERVER_ADDR_KEY = "gnmi_ip";
+    private static final String GNMI_SERVER_PORT_KEY = "gnmi_port";
 
     protected final Logger log = LoggerFactory.getLogger(getClass());
     protected DeviceId deviceId;
@@ -66,7 +65,7 @@
         return true;
     }
 
-    protected GnmiClient createClient() {
+    GnmiClient createClient() {
         deviceId = handler().data().deviceId();
         controller = handler().get(GnmiController.class);
 
@@ -74,7 +73,7 @@
         final String serverPortString = this.data().value(GNMI_SERVER_PORT_KEY);
 
         if (serverAddr == null || serverPortString == null) {
-            log.warn("Unable to create client for {}, missing driver data key (required is {}, {}, and {})",
+            log.warn("Unable to create client for {}, missing driver data key (required is {} and {})",
                     deviceId, GNMI_SERVER_ADDR_KEY, GNMI_SERVER_PORT_KEY);
             return null;
         }
@@ -83,11 +82,10 @@
         try {
             serverPort = Integer.parseUnsignedInt(serverPortString);
         } catch (NumberFormatException e) {
-            log.error("{} is not a valid gNMI port number", serverPortString);
+            log.error("{} is not a valid port number", serverPortString);
             return null;
         }
-        GnmiClientKey clientKey =
-                new GnmiClientKey(GNMI_SERVICE_NAME, deviceId, serverAddr, serverPort);
+        GnmiClientKey clientKey = new GnmiClientKey(deviceId, serverAddr, serverPort);
         if (!controller.createClient(clientKey)) {
             log.warn("Unable to create client for {}, aborting operation", deviceId);
             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 d1ee1b8..c443460 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
@@ -77,7 +77,9 @@
             return false;
         }
 
-        return getFutureWithDeadline(client.isServiceAvailable(), "getting availability", false);
+        return getFutureWithDeadline(
+                client.isServiceAvailable(),
+                "checking if gNMI service is available", false);
     }
 
     @Override
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 f9d2236..526c333 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
@@ -30,13 +30,9 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
 import static gnmi.Gnmi.Path;
 import static gnmi.Gnmi.PathElem;
@@ -50,8 +46,6 @@
         extends AbstractGnmiHandlerBehaviour
         implements DeviceDescriptionDiscovery {
 
-    private static final int REQUEST_TIMEOUT_SECONDS = 5;
-
     private static final Logger log = LoggerFactory
             .getLogger(OpenConfigGnmiDeviceDescriptionDiscovery.class);
 
@@ -67,18 +61,12 @@
         }
         log.debug("Discovering port details on device {}", handler().data().deviceId());
 
-        GetResponse response;
-        try {
-            response = client.get(buildPortStateRequest())
-                    .get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
-        } catch (InterruptedException | ExecutionException | TimeoutException e) {
-            log.warn("Unable to discover ports from {}: {}", deviceId, e.getMessage());
-            log.debug("{}", e);
-            return Collections.emptyList();
-        }
+        final GetResponse response = getFutureWithDeadline(
+                client.get(buildPortStateRequest()),
+                "getting port details", GetResponse.getDefaultInstance());
 
-        Map<String, DefaultPortDescription.Builder> ports = Maps.newHashMap();
-        Map<String, DefaultAnnotations.Builder> annotations = Maps.newHashMap();
+        final Map<String, DefaultPortDescription.Builder> ports = Maps.newHashMap();
+        final Map<String, DefaultAnnotations.Builder> annotations = Maps.newHashMap();
 
         // Creates port descriptions with port name and port number
         response.getNotificationList()
@@ -86,19 +74,18 @@
                 .flatMap(notification -> notification.getUpdateList().stream())
                 .forEach(update -> {
                     // /interfaces/interface[name=ifName]/state/...
-                    String ifName = update.getPath().getElem(1).getKeyMap().get("name");
+                    final String ifName = update.getPath().getElem(1)
+                            .getKeyMap().get("name");
                     if (!ports.containsKey(ifName)) {
                         ports.put(ifName, DefaultPortDescription.builder());
                         annotations.put(ifName, DefaultAnnotations.builder());
                     }
-
-
-                    DefaultPortDescription.Builder builder = ports.get(ifName);
-                    DefaultAnnotations.Builder annotationsBuilder = annotations.get(ifName);
+                    final DefaultPortDescription.Builder builder = ports.get(ifName);
+                    final DefaultAnnotations.Builder annotationsBuilder = annotations.get(ifName);
                     parseInterfaceInfo(update, ifName, builder, annotationsBuilder);
                 });
 
-        List<PortDescription> portDescriptionList = Lists.newArrayList();
+        final List<PortDescription> portDescriptionList = Lists.newArrayList();
         ports.forEach((key, value) -> {
             DefaultAnnotations annotation = annotations.get(key).build();
             portDescriptionList.add(value.annotations(annotation).build());
@@ -122,7 +109,7 @@
     /**
      * Parses the interface information.
      *
-     * @param update           the update received
+     * @param update the update received
      */
     private void parseInterfaceInfo(Update update,
                                     String ifName,
@@ -130,45 +117,32 @@
                                     DefaultAnnotations.Builder annotationsBuilder) {
 
 
-        Path path = update.getPath();
-        List<PathElem> elems = path.getElemList();
-        Gnmi.TypedValue val = update.getVal();
+        final Path path = update.getPath();
+        final List<PathElem> elems = path.getElemList();
+        final Gnmi.TypedValue val = update.getVal();
         if (elems.size() == 4) {
             // /interfaces/interface/state/ifindex
             // /interfaces/interface/state/oper-status
-            String pathElemName = elems.get(3).getName();
+            final String pathElemName = elems.get(3).getName();
             switch (pathElemName) {
                 case "ifindex": // port number
                     builder.withPortNumber(PortNumber.portNumber(val.getUintVal(), ifName));
-                    break;
+                    return;
                 case "oper-status":
                     builder.isEnabled(parseOperStatus(val.getStringVal()));
-                    break;
+                    return;
                 default:
-                    String valueString = val.toByteString().toString(Charset.defaultCharset()).trim();
-                    if (!valueString.isEmpty()) {
-                        annotationsBuilder.set(pathElemName, valueString);
-                    }
-                    log.debug("Unknown path: {}", path);
                     break;
             }
-        }
-        if (elems.size() == 5) {
+        } else if (elems.size() == 5) {
             // /interfaces/interface/ethernet/config/port-speed
-            String pathElemName = elems.get(4).getName();
-            switch (pathElemName) {
-                case "port-speed":
-                    builder.portSpeed(parsePortSpeed(val.getStringVal()));
-                    break;
-                default:
-                    String valueString = val.toByteString().toString(Charset.defaultCharset()).trim();
-                    if (!valueString.isEmpty()) {
-                        annotationsBuilder.set(pathElemName, valueString);
-                    }
-                    log.debug("Unknown path: {}", path);
-                    break;
+            final String pathElemName = elems.get(4).getName();
+            if (pathElemName.equals("port-speed")) {
+                builder.portSpeed(parsePortSpeed(val.getStringVal()));
+                return;
             }
         }
+        log.debug("Unknown path when parsing interface info: {}", path);
     }
 
     private boolean parseOperStatus(String operStatus) {
@@ -201,6 +175,7 @@
             case "SPEED_100GB":
                 return 100000;
             default:
+                log.warn("Unrecognized port speed string '{}'", speed);
                 return 1000;
         }
     }
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 90e7a31..7f9c8c6 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
@@ -48,9 +48,9 @@
     private static final String DEVICE_REQ_TIMEOUT = "deviceRequestTimeout";
     private static final int DEFAULT_DEVICE_REQ_TIMEOUT = 60;
 
-    public static final String P4RUNTIME_SERVER_ADDR_KEY = "p4runtime_ip";
-    public static final String P4RUNTIME_SERVER_PORT_KEY = "p4runtime_port";
-    public static final String P4RUNTIME_DEVICE_ID_KEY = "p4runtime_deviceId";
+    private static final String P4RUNTIME_SERVER_ADDR_KEY = "p4runtime_ip";
+    private static final String P4RUNTIME_SERVER_PORT_KEY = "p4runtime_port";
+    private static final String P4RUNTIME_DEVICE_ID_KEY = "p4runtime_deviceId";
 
     protected final Logger log = LoggerFactory.getLogger(getClass());
 
@@ -61,7 +61,7 @@
     protected P4RuntimeController controller;
     protected PiPipeconf pipeconf;
     protected P4RuntimeClient client;
-    protected PiTranslationService piTranslationService;
+    protected PiTranslationService translationService;
 
     /**
      * Initializes this behaviour attributes. Returns true if the operation was
@@ -102,7 +102,7 @@
         }
         pipeconf = piPipeconfService.getPipeconf(pipeconfId).get();
 
-        piTranslationService = handler().get(PiTranslationService.class);
+        translationService = handler().get(PiTranslationService.class);
 
         return true;
     }
@@ -158,8 +158,8 @@
             return null;
         }
 
-        P4RuntimeClientKey clientKey = new
-                P4RuntimeClientKey(deviceId, serverAddr, serverPort, p4DeviceId);
+        final P4RuntimeClientKey clientKey = new P4RuntimeClientKey(
+                deviceId, serverAddr, serverPort, p4DeviceId);
         if (!controller.createClient(clientKey)) {
             log.warn("Unable to create client for {}, aborting operation", deviceId);
             return null;
@@ -176,7 +176,7 @@
      * @param defaultVal default value
      * @return boolean
      */
-    protected boolean driverBoolProperty(String propName, boolean defaultVal) {
+    boolean driverBoolProperty(String propName, boolean defaultVal) {
         checkNotNull(propName);
         if (handler().driver().getProperty(propName) == null) {
             return defaultVal;
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 45be79e..c20c899 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
@@ -96,7 +96,7 @@
         groupMirror = this.handler().get(P4RuntimeGroupMirror.class);
         memberMirror = this.handler().get(P4RuntimeActionProfileMemberMirror.class);
         groupStore = handler().get(GroupStore.class);
-        groupTranslator = piTranslationService.groupTranslator();
+        groupTranslator = translationService.groupTranslator();
         return true;
     }
 
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 5a233ef..693353f 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
@@ -118,7 +118,7 @@
 
         pipelineModel = pipeconf.pipelineModel();
         tableMirror = handler().get(P4RuntimeTableMirror.class);
-        translator = piTranslationService.flowRuleTranslator();
+        translator = translationService.flowRuleTranslator();
         return true;
     }
 
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 39ed84d..f2dff80 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
@@ -74,7 +74,7 @@
             return false;
         }
 
-        translator = piTranslationService.meterTranslator();
+        translator = translationService.meterTranslator();
         meterMirror = handler().get(P4RuntimeMeterMirror.class);
         pipelineModel = pipeconf.pipelineModel();
         return true;
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 a5c3538..ecdda08 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
@@ -66,7 +66,7 @@
         }
         mcGroupMirror = this.handler().get(P4RuntimeMulticastGroupMirror.class);
         groupStore = handler().get(GroupStore.class);
-        mcGroupTranslator = piTranslationService.multicastGroupTranslator();
+        mcGroupTranslator = translationService.multicastGroupTranslator();
         return true;
     }
 
diff --git a/drivers/stratum/BUILD b/drivers/stratum/BUILD
new file mode 100644
index 0000000..41db9f1
--- /dev/null
+++ b/drivers/stratum/BUILD
@@ -0,0 +1,25 @@
+COMPILE_DEPS = CORE_DEPS + KRYO + JACKSON + [
+    "@io_grpc_grpc_java//core",
+    "//drivers/p4runtime:onos-drivers-p4runtime",
+    "//drivers/gnmi:onos-drivers-gnmi",
+    "//pipelines/basic:onos-pipelines-basic",
+]
+
+osgi_jar(
+    resources = glob(["src/main/resources/**"]),
+    resources_root = "src/main/resources",
+    deps = COMPILE_DEPS,
+)
+
+onos_app(
+    app_name = "org.onosproject.drivers.stratum",
+    category = "Drivers",
+    description = "Adds support for Stratum-based devices",
+    required_apps = [
+        "org.onosproject.generaldeviceprovider",
+        "org.onosproject.drivers.gnmi",
+        "org.onosproject.drivers.p4runtime",
+    ],
+    title = "Stratum Drivers",
+    url = "http://onosproject.org",
+)
diff --git a/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumDriversLoader.java b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumDriversLoader.java
new file mode 100644
index 0000000..a68729a
--- /dev/null
+++ b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumDriversLoader.java
@@ -0,0 +1,31 @@
+/*
+ * 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.stratum;
+
+import org.onosproject.net.driver.AbstractDriverLoader;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * Loader for Stratum Switch device drivers.
+ */
+@Component(immediate = true)
+public class StratumDriversLoader extends AbstractDriverLoader {
+
+    public StratumDriversLoader() {
+        super("/stratum-drivers.xml");
+    }
+}
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
new file mode 100644
index 0000000..4e169a5
--- /dev/null
+++ b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/StratumHandshaker.java
@@ -0,0 +1,135 @@
+/*
+ * 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.stratum;
+
+import io.grpc.StatusRuntimeException;
+import org.onosproject.drivers.gnmi.GnmiHandshaker;
+import org.onosproject.drivers.p4runtime.P4RuntimeHandshaker;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.device.DeviceAgentListener;
+import org.onosproject.net.device.DeviceHandshaker;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.driver.DriverData;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.provider.ProviderId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Implementation of DeviceHandshaker for Stratum device.
+ */
+public class StratumHandshaker extends AbstractHandlerBehaviour implements DeviceHandshaker {
+
+    private static final Logger log = LoggerFactory.getLogger(StratumHandshaker.class);
+    private static final int DEFAULT_DEVICE_REQ_TIMEOUT = 10;
+
+    private P4RuntimeHandshaker p4RuntimeHandshaker;
+    private GnmiHandshaker gnmiHandshaker;
+    private DeviceId deviceId;
+
+    public StratumHandshaker() {
+        p4RuntimeHandshaker = new P4RuntimeHandshaker();
+        gnmiHandshaker = new GnmiHandshaker();
+    }
+
+    @Override
+    public void setHandler(DriverHandler handler) {
+        super.setHandler(handler);
+        p4RuntimeHandshaker.setHandler(handler);
+        gnmiHandshaker.setHandler(handler);
+    }
+
+    @Override
+    public void setData(DriverData data) {
+        super.setData(data);
+        p4RuntimeHandshaker.setData(data);
+        gnmiHandshaker.setData(data);
+        deviceId = data.deviceId();
+    }
+
+    @Override
+    public CompletableFuture<Boolean> isReachable() {
+        return p4RuntimeHandshaker.isReachable()
+                .thenCombine(gnmiHandshaker.isReachable(), Boolean::logicalAnd);
+    }
+
+    @Override
+    public void roleChanged(MastershipRole newRole) {
+        p4RuntimeHandshaker.roleChanged(newRole);
+        // gNMI doesn't support mastership handling.
+    }
+
+    @Override
+    public MastershipRole getRole() {
+        return p4RuntimeHandshaker.getRole();
+    }
+
+    @Override
+    public void addDeviceAgentListener(ProviderId providerId, DeviceAgentListener listener) {
+        p4RuntimeHandshaker.addDeviceAgentListener(providerId, listener);
+    }
+
+    @Override
+    public void removeDeviceAgentListener(ProviderId providerId) {
+        p4RuntimeHandshaker.removeDeviceAgentListener(providerId);
+    }
+
+    @Override
+    public CompletableFuture<Boolean> connect() {
+        return p4RuntimeHandshaker.connect()
+                .thenCombine(gnmiHandshaker.connect(), Boolean::logicalAnd);
+    }
+
+    @Override
+    public boolean isConnected() {
+        final CompletableFuture<Boolean> p4runtimeConnected =
+                CompletableFuture.supplyAsync(p4RuntimeHandshaker::isConnected);
+        final CompletableFuture<Boolean> gnmiConnected =
+                CompletableFuture.supplyAsync(gnmiHandshaker::isConnected);
+
+        try {
+            return p4runtimeConnected
+                    .thenCombine(gnmiConnected, Boolean::logicalAnd)
+                    .get(DEFAULT_DEVICE_REQ_TIMEOUT, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            log.error("Exception while checking connectivity on {}", data().deviceId());
+        } catch (ExecutionException e) {
+            final Throwable cause = e.getCause();
+            if (cause instanceof StatusRuntimeException) {
+                final StatusRuntimeException grpcError = (StatusRuntimeException) cause;
+                log.warn("Error while checking connectivity on {}: {}", deviceId, grpcError.getMessage());
+            } else {
+                log.error("Exception while checking connectivity on {}", deviceId, e.getCause());
+            }
+        } catch (TimeoutException e) {
+            log.error("Operation TIMEOUT while checking connectivity on {}", deviceId);
+        }
+        return false;
+    }
+
+    @Override
+    public CompletableFuture<Boolean> disconnect() {
+        return p4RuntimeHandshaker.disconnect()
+                .thenCombine(gnmiHandshaker.disconnect(), Boolean::logicalAnd);
+    }
+}
diff --git a/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/dummy/StratumDummyPipelineProgrammable.java b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/dummy/StratumDummyPipelineProgrammable.java
new file mode 100644
index 0000000..e07e3b3
--- /dev/null
+++ b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/dummy/StratumDummyPipelineProgrammable.java
@@ -0,0 +1,44 @@
+/*
+ * 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.stratum.dummy;
+
+import org.onosproject.drivers.p4runtime.AbstractP4RuntimePipelineProgrammable;
+import org.onosproject.net.behaviour.PiPipelineProgrammable;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.pipelines.basic.PipeconfLoader;
+
+import java.nio.ByteBuffer;
+import java.util.Optional;
+
+/**
+ * Implementation of the PiPipelineProgrammable behavior for Dummy Stratum Switch.
+ */
+public class StratumDummyPipelineProgrammable
+        extends AbstractP4RuntimePipelineProgrammable
+        implements PiPipelineProgrammable {
+
+    @Override
+    public ByteBuffer createDeviceDataBuffer(PiPipeconf pipeconf) {
+        // No pipeline binary needed
+        return ByteBuffer.allocate(1);
+    }
+
+    @Override
+    public Optional<PiPipeconf> getDefaultPipeconf() {
+        return Optional.of(PipeconfLoader.BASIC_PIPECONF);
+    }
+}
diff --git a/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/dummy/package-info.java b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/dummy/package-info.java
new file mode 100644
index 0000000..7633706
--- /dev/null
+++ b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/dummy/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Behaviours for Dummy Stratum Device.
+ */
+
+package org.onosproject.drivers.stratum.dummy;
diff --git a/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/package-info.java b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/package-info.java
new file mode 100644
index 0000000..145022a
--- /dev/null
+++ b/drivers/stratum/src/main/java/org/onosproject/drivers/stratum/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Driver for Stratum devices.
+ */
+package org.onosproject.drivers.stratum;
diff --git a/drivers/stratum/src/main/resources/stratum-drivers.xml b/drivers/stratum/src/main/resources/stratum-drivers.xml
new file mode 100644
index 0000000..83fea1d
--- /dev/null
+++ b/drivers/stratum/src/main/resources/stratum-drivers.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2017-present Open Networking Foundation
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<drivers>
+    <driver name="stratum" manufacturer="Open Networking Foundation"
+            hwVersion="master" swVersion="Stratum" extends="p4runtime,gnmi">
+        <behaviour api="org.onosproject.net.device.DeviceHandshaker"
+                   impl="org.onosproject.drivers.stratum.StratumHandshaker"/>
+    </driver>
+
+    <driver name="stratum-dummy" manufacturer="Open Networking Foundation"
+            hwVersion="Dummy" swVersion="Stratum" extends="stratum">
+        <behaviour api="org.onosproject.net.behaviour.PiPipelineProgrammable"
+                   impl="org.onosproject.drivers.stratum.dummy.StratumDummyPipelineProgrammable"/>
+        <property name="tableReadFromMirror">true</property>
+        <property name="actionGroupReadFromMirror">true</property>
+    </driver>
+</drivers>
+
diff --git a/protocols/gnmi/api/src/main/java/org/onosproject/gnmi/api/GnmiClient.java b/protocols/gnmi/api/src/main/java/org/onosproject/gnmi/api/GnmiClient.java
index 242bc94..5beb238 100644
--- a/protocols/gnmi/api/src/main/java/org/onosproject/gnmi/api/GnmiClient.java
+++ b/protocols/gnmi/api/src/main/java/org/onosproject/gnmi/api/GnmiClient.java
@@ -17,10 +17,9 @@
 package org.onosproject.gnmi.api;
 
 import com.google.common.annotations.Beta;
-
 import gnmi.Gnmi.CapabilityResponse;
-import gnmi.Gnmi.GetResponse;
 import gnmi.Gnmi.GetRequest;
+import gnmi.Gnmi.GetResponse;
 import gnmi.Gnmi.SetRequest;
 import gnmi.Gnmi.SetResponse;
 import org.onosproject.grpc.api.GrpcClient;
@@ -34,15 +33,14 @@
 public interface GnmiClient extends GrpcClient {
 
     /**
-     * Gets capability from a target. This allows device driver behavior
-     * to validate the service version and models which gNMI device supported.
+     * Gets capability from a target.
      *
      * @return the capability response
      */
     CompletableFuture<CapabilityResponse> capability();
 
     /**
-     * Retrieve a snapshot of data from the device.
+     * Retrieves a snapshot of data from the device.
      *
      * @param request the get request
      * @return the snapshot of data from the device
diff --git a/protocols/gnmi/api/src/main/java/org/onosproject/gnmi/api/GnmiClientKey.java b/protocols/gnmi/api/src/main/java/org/onosproject/gnmi/api/GnmiClientKey.java
index 26ce113..bc18b9a 100644
--- a/protocols/gnmi/api/src/main/java/org/onosproject/gnmi/api/GnmiClientKey.java
+++ b/protocols/gnmi/api/src/main/java/org/onosproject/gnmi/api/GnmiClientKey.java
@@ -26,15 +26,16 @@
 @Beta
 public class GnmiClientKey extends GrpcClientKey {
 
+    private static final String GNMI = "gnmi";
+
     /**
      * Creates a new gNMI client key.
      *
-     * @param serviceName gNMI service name of the client
      * @param deviceId    ONOS device ID
      * @param serverAddr  gNMI server address
      * @param serverPort  gNMI server port
      */
-    public GnmiClientKey(String serviceName, DeviceId deviceId, String serverAddr, int serverPort) {
-        super(serviceName, deviceId, serverAddr, serverPort);
+    public GnmiClientKey(DeviceId deviceId, String serverAddr, int serverPort) {
+        super(GNMI, deviceId, serverAddr, serverPort);
     }
 }
diff --git a/protocols/gnmi/ctl/src/main/java/org/onosproject/gnmi/ctl/GnmiClientImpl.java b/protocols/gnmi/ctl/src/main/java/org/onosproject/gnmi/ctl/GnmiClientImpl.java
index b8179a9..22b226b 100644
--- a/protocols/gnmi/ctl/src/main/java/org/onosproject/gnmi/ctl/GnmiClientImpl.java
+++ b/protocols/gnmi/ctl/src/main/java/org/onosproject/gnmi/ctl/GnmiClientImpl.java
@@ -27,10 +27,10 @@
 import io.grpc.ManagedChannel;
 import io.grpc.Status;
 import io.grpc.StatusRuntimeException;
+import org.onosproject.gnmi.api.GnmiClient;
 import org.onosproject.gnmi.api.GnmiClientKey;
 import org.onosproject.grpc.ctl.AbstractGrpcClient;
 import org.slf4j.Logger;
-import org.onosproject.gnmi.api.GnmiClient;
 
 import java.util.concurrent.CompletableFuture;
 
@@ -46,8 +46,8 @@
     private final Logger log = getLogger(getClass());
     private final gNMIGrpc.gNMIBlockingStub blockingStub;
 
-    public GnmiClientImpl(GnmiClientKey clientKey, ManagedChannel managedChannel) {
-        super(clientKey, managedChannel);
+    GnmiClientImpl(GnmiClientKey clientKey, ManagedChannel managedChannel) {
+        super(clientKey);
         this.blockingStub = gNMIGrpc.newBlockingStub(managedChannel);
     }
 
@@ -101,8 +101,7 @@
 
     private boolean doServiceAvailable() {
         try {
-            blockingStub.get(DUMMY_REQUEST);
-            return true;
+            return blockingStub.get(DUMMY_REQUEST) != null;
         } catch (StatusRuntimeException e) {
             // This gRPC call should throw INVALID_ARGUMENT status exception
             // since "/onos-gnmi-test" path does not exists in any config model
diff --git a/protocols/grpc/api/src/main/java/org/onosproject/grpc/api/GrpcClientController.java b/protocols/grpc/api/src/main/java/org/onosproject/grpc/api/GrpcClientController.java
index 2e3ea5d..3cdbcd1 100644
--- a/protocols/grpc/api/src/main/java/org/onosproject/grpc/api/GrpcClientController.java
+++ b/protocols/grpc/api/src/main/java/org/onosproject/grpc/api/GrpcClientController.java
@@ -20,8 +20,8 @@
 import org.onosproject.net.DeviceId;
 
 /**
- * Abstraction of a gRPC controller which controls specific gRPC
- * client {@link C} with specific client key {@link K}.
+ * Abstraction of a gRPC controller which controls specific gRPC client {@link
+ * C} with specific client key {@link K}.
  *
  * @param <K> the gRPC client key
  * @param <C> the gRPC client type
@@ -30,19 +30,19 @@
 public interface GrpcClientController<K extends GrpcClientKey, C extends GrpcClient> {
 
     /**
-     * Instantiates a new client to operate on a gRPC server identified by
-     * the given information. As a result of this method, a client can be later
+     * Instantiates a new client to operate on a gRPC server identified by the
+     * given information. As a result of this method, a client can be later
      * obtained by invoking {@link #getClient(DeviceId)}.
-     *
-     * Only one client can exist for the same device ID. Calls to this method are
-     * idempotent fot the same client key, i.e. returns true
-     * if such client already exists but a new one is not created.
-     * If there exists a client with same device ID but different address and port,
-     * removes old one and recreate new one.
+     * <p>
+     * Only one client can exist for the same device ID. Calls to this method
+     * are idempotent fot the same client key, i.e. returns true if such client
+     * already exists but a new one is not created. If there exists a client
+     * with same device ID but different address and port, removes old one and
+     * recreate new one.
      *
      * @param clientKey the client key
-     * @return true if the client was created and the channel to the server is open;
-     *         false otherwise
+     * @return true if the client was created and the channel to the server is
+     * open; false otherwise
      */
     boolean createClient(K clientKey);
 
@@ -55,8 +55,8 @@
     C getClient(DeviceId deviceId);
 
     /**
-     * Removes the gRPC client for the given device. If no client
-     * exists for the given device, the result is a no-op.
+     * Removes the gRPC client for the given device. If no client exists for the
+     * given device, the result is a no-op.
      *
      * @param deviceId the device identifier
      */
@@ -64,15 +64,15 @@
 
     /**
      * Check reachability of the gRPC server running on the given device.
-     * Reachability can be tested only if a client is previously created
-     * using {@link #createClient(GrpcClientKey)}.
-     * Note that this only checks the reachability instead of checking service
-     * availability, different gRPC client checks service availability with
+     * Reachability can be tested only if a client is previously created using
+     * {@link #createClient(GrpcClientKey)}. Note that this only checks the
+     * reachability instead of checking service availability, different
+     * service-specific gRPC clients might check service availability in a
      * different way.
      *
      * @param deviceId the device identifier
-     * @return true if client was created and is able to contact the gNMI server;
-     *         false otherwise
+     * @return true if client was created and is able to contact the gNMI
+     * server; false otherwise
      */
     boolean isReachable(DeviceId deviceId);
 }
diff --git a/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClient.java b/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClient.java
index 4764a56..05e2978 100644
--- a/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClient.java
+++ b/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClient.java
@@ -17,7 +17,6 @@
 package org.onosproject.grpc.ctl;
 
 import io.grpc.Context;
-import io.grpc.ManagedChannel;
 import io.grpc.StatusRuntimeException;
 import org.onlab.util.SharedExecutors;
 import org.onosproject.grpc.api.GrpcClient;
@@ -25,7 +24,6 @@
 import org.onosproject.net.DeviceId;
 import org.slf4j.Logger;
 
-
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
@@ -45,26 +43,24 @@
 public abstract class AbstractGrpcClient implements GrpcClient {
 
     // Timeout in seconds to obtain the request lock.
-    protected static final int LOCK_TIMEOUT = 60;
+    private static final int LOCK_TIMEOUT = 60;
     private static final int DEFAULT_THREAD_POOL_SIZE = 10;
 
     protected final Logger log = getLogger(getClass());
 
-    protected final Lock requestLock = new ReentrantLock();
-    protected final Context.CancellableContext cancellableContext =
+    private final Lock requestLock = new ReentrantLock();
+    private final Context.CancellableContext cancellableContext =
             Context.current().withCancellation();
+    private final Executor contextExecutor;
+
     protected final ExecutorService executorService;
-    protected final Executor contextExecutor;
+    protected final DeviceId deviceId;
 
-    protected ManagedChannel channel;
-    protected DeviceId deviceId;
-
-    protected AbstractGrpcClient(GrpcClientKey clientKey, ManagedChannel channel) {
+    protected AbstractGrpcClient(GrpcClientKey clientKey) {
         this.deviceId = clientKey.deviceId();
         this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE, groupedThreads(
                 "onos-grpc-" + clientKey.serviceName() + "-client-" + deviceId.toString(), "%d"));
         this.contextExecutor = this.cancellableContext.fixedContextExecutor(executorService);
-        this.channel = channel;
     }
 
     @Override
@@ -112,7 +108,7 @@
      * @param executor the executor to execute this supplier
      * @return CompletableFuture includes the result of supplier
      */
-    protected <U> CompletableFuture<U> supplyWithExecutor(
+    private <U> CompletableFuture<U> supplyWithExecutor(
             Supplier<U> supplier, String opDescription, Executor executor) {
         return CompletableFuture.supplyAsync(() -> {
             // TODO: explore a more relaxed locking strategy.
diff --git a/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClientController.java b/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClientController.java
index 36e453c..bf2bbf1 100644
--- a/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClientController.java
+++ b/protocols/grpc/ctl/src/main/java/org/onosproject/grpc/ctl/AbstractGrpcClientController.java
@@ -21,7 +21,6 @@
 import io.grpc.ManagedChannel;
 import io.grpc.ManagedChannelBuilder;
 import io.grpc.netty.NettyChannelBuilder;
-
 import org.onosproject.event.AbstractListenerManager;
 import org.onosproject.event.Event;
 import org.onosproject.event.EventListener;
@@ -152,7 +151,7 @@
         return withDeviceLock(() -> doGetClient(deviceId), deviceId);
     }
 
-    protected C doGetClient(DeviceId deviceId) {
+    private C doGetClient(DeviceId deviceId) {
         if (!clientKeys.containsKey(deviceId)) {
             return null;
         }
@@ -183,7 +182,7 @@
         return withDeviceLock(() -> doIsReachable(deviceId), deviceId);
     }
 
-    protected boolean doIsReachable(DeviceId deviceId) {
+    private boolean doIsReachable(DeviceId deviceId) {
         // Default behaviour checks only the gRPC channel, should
         // check according to different gRPC service
         if (!clientKeys.containsKey(deviceId)) {
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
index 53f7668..d5080ae 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
@@ -138,7 +138,7 @@
     P4RuntimeClientImpl(P4RuntimeClientKey clientKey, ManagedChannel channel,
                         P4RuntimeControllerImpl controller) {
 
-        super(clientKey, channel);
+        super(clientKey);
         this.p4DeviceId = clientKey.p4DeviceId();
         this.controller = controller;
 
diff --git a/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java b/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
index 4374089..3ca56ee 100644
--- a/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
+++ b/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
@@ -106,7 +106,12 @@
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.net.device.DeviceEvent.Type;
-import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.*;
+import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.OP_TIMEOUT_SHORT;
+import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.OP_TIMEOUT_SHORT_DEFAULT;
+import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.PROBE_FREQUENCY;
+import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.PROBE_FREQUENCY_DEFAULT;
+import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_FREQUENCY;
+import static org.onosproject.provider.general.device.impl.OsgiPropertyConstants.STATS_POLL_FREQUENCY_DEFAULT;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -498,13 +503,16 @@
         // Get one from driver or forge.
         final DeviceDescriptionDiscovery deviceDiscovery = getBehaviour(
                 deviceId, DeviceDescriptionDiscovery.class);
-        if (deviceDiscovery != null) {
-            // Enforce defaultAvailable flag over the one obtained from driver.
-            final DeviceDescription d = deviceDiscovery.discoverDeviceDetails();
-            return new DefaultDeviceDescription(d, defaultAvailable, d.annotations());
-        } else {
+        if (deviceDiscovery == null) {
             return forgeDeviceDescription(deviceId, defaultAvailable);
         }
+
+        final DeviceDescription d = deviceDiscovery.discoverDeviceDetails();
+        if (d == null) {
+            return forgeDeviceDescription(deviceId, defaultAvailable);
+        }
+        // Enforce defaultAvailable flag over the one obtained from driver.
+        return new DefaultDeviceDescription(d, defaultAvailable, d.annotations());
     }
 
     private List<PortDescription> getPortDetails(DeviceId deviceId) {
diff --git a/tools/build/bazel/modules.bzl b/tools/build/bazel/modules.bzl
index 330d9fd..12a54e9 100644
--- a/tools/build/bazel/modules.bzl
+++ b/tools/build/bazel/modules.bzl
@@ -109,6 +109,7 @@
     "//drivers/polatis/netconf:onos-drivers-polatis-netconf-oar",
     "//drivers/polatis/openflow:onos-drivers-polatis-openflow-oar",
     "//drivers/odtn-driver:onos-drivers-odtn-driver-oar",
+    "//drivers/stratum:onos-drivers-stratum-oar",
 ]
 
 ONOS_PROVIDERS = [