Recover missing pipeconf-merged driver at component activation

needed to support rebooting ONOS nodes

Change-Id: I44d34c649750ffc3d6b0205ee02c8c88391f1f8a
(cherry picked from commit ccee77a024c14c3cef68457028ea71edc0555f54)
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiPipeconfMappingStore.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiPipeconfMappingStore.java
index 2f275ee..4a9bc90 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/service/PiPipeconfMappingStore.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiPipeconfMappingStore.java
@@ -31,7 +31,7 @@
 public interface PiPipeconfMappingStore extends Store<PiPipeconfDeviceMappingEvent, PiPipeconfMappingStoreDelegate> {
 
     /**
-     * Retrieves the id of the pipeconf deployed on a given device.
+     * Retrieves the id of the pipeconf associated to a given device.
      *
      * @param deviceId device identifier
      * @return PiPipeconfId
diff --git a/core/net/src/main/java/org/onosproject/net/driver/impl/DriverManager.java b/core/net/src/main/java/org/onosproject/net/driver/impl/DriverManager.java
index f2ece6b..f098c75 100644
--- a/core/net/src/main/java/org/onosproject/net/driver/impl/DriverManager.java
+++ b/core/net/src/main/java/org/onosproject/net/driver/impl/DriverManager.java
@@ -120,21 +120,14 @@
 
         Driver driver;
 
-        // Primary source of driver configuration is the network config.
+        // Special processing for devices with pipeconf.
         if (pipeconfService.ofDevice(deviceId).isPresent()) {
-            // Device has pipeconf associated, look for merged driver.
-            // Implementation of PiPipeconfService is expected to look for a
-            // base driver in network config.
-            PiPipeconfId pipeconfId = pipeconfService.ofDevice(deviceId).get();
-            String mergedDriver = pipeconfService.getMergedDriver(deviceId, pipeconfId);
-            driver = mergedDriver != null ? lookupDriver(mergedDriver) : null;
-            if (driver != null) {
-                return driver;
-            } else {
-                log.error("Merged driver for {} with pipeconf {} not found, falling back.",
-                          deviceId, pipeconfId);
-            }
+            // No fallback for pipeconf merged drivers. Returns null if driver
+            // does not exist.
+            return getPipeconfMergedDriver(deviceId);
         }
+
+        // Primary source of driver configuration is the network config.
         BasicDeviceConfig cfg = networkConfigService.getConfig(deviceId, BasicDeviceConfig.class);
         driver = lookupDriver(cfg != null ? cfg.driver() : null);
         if (driver != null) {
@@ -155,6 +148,28 @@
                               NO_DRIVER);
     }
 
+    private Driver getPipeconfMergedDriver(DeviceId deviceId) {
+        PiPipeconfId pipeconfId = pipeconfService.ofDevice(deviceId).orElse(null);
+        if (pipeconfId == null) {
+            log.warn("Missing pipeconf for {}, cannot produce a pipeconf merged driver",
+                      deviceId);
+            return null;
+        }
+        String mergedDriverName = pipeconfService.getMergedDriver(deviceId, pipeconfId);
+        if (mergedDriverName == null) {
+            log.warn("Unable to get pipeconf merged driver for {} and {}",
+                     deviceId, pipeconfId);
+            return null;
+        }
+        try {
+            return getDriver(mergedDriverName);
+        } catch (ItemNotFoundException e) {
+            log.warn("Specified pipeconf merged driver {} for {} not found",
+                     mergedDriverName, deviceId);
+            return null;
+        }
+    }
+
     private Driver lookupDriver(String driverName) {
         if (driverName != null) {
             try {
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java
index a90149f..5044df0 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java
@@ -27,6 +27,7 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.util.ItemNotFoundException;
+import org.onlab.util.SharedExecutors;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigRegistry;
@@ -55,6 +56,7 @@
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
 
 import static java.lang.String.format;
@@ -75,6 +77,8 @@
     private static final String MERGED_DRIVER_SEPARATOR = ":";
     private static final String CFG_SCHEME = "piPipeconf";
 
+    private static final int MISSING_DRIVER_WATCHDOG_INTERVAL = 5; // Seconds.
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected NetworkConfigRegistry cfgService;
 
@@ -111,6 +115,13 @@
         cfgService.registerConfigFactory(configFactory);
         driverAdminService.addListener(driverListener);
         checkMissingMergedDrivers();
+        if (!missingMergedDrivers.isEmpty()) {
+            // Missing drivers should be created upon detecting registration
+            // events of a new pipeconf or a base driver. If, for any reason, we
+            // miss such event, here's a watchdog task.
+            SharedExecutors.getPoolThreadExecutor()
+                    .execute(this::missingDriversWatchdogTask);
+        }
         log.info("Started");
     }
 
@@ -134,7 +145,7 @@
         }
         pipeconfs.put(pipeconf.id(), pipeconf);
         log.info("New pipeconf registered: {}", pipeconf.id());
-        executor.execute(() -> mergeAll(pipeconf.id()));
+        executor.execute(() -> attemptMergeAll(pipeconf.id()));
     }
 
     @Override
@@ -306,64 +317,102 @@
         }
     }
 
-    private void checkMissingMergedDrivers() {
-        cfgService.getSubjects(DeviceId.class, BasicDeviceConfig.class).stream()
-                .map(d -> cfgService.getConfig(d, BasicDeviceConfig.class))
-                .map(BasicDeviceConfig::driver)
-                .filter(Objects::nonNull)
-                .filter(d -> getDriver(d) == null)
-                .forEach(driverName -> {
-                    final String baseDriverName = getBaseDriverNameFromMerged(driverName);
-                    final PiPipeconfId pipeconfId = getPipeconfIdFromMerged(driverName);
-                    if (baseDriverName == null || pipeconfId == null) {
-                        // Not a merged driver.
-                        return;
-                    }
-                    log.info("Detected missing merged driver: {}", driverName);
-                    missingMergedDrivers.add(driverName);
-                    // Attempt building the driver now if all pieces are present.
-                    // If not, either a driver or pipeconf event will re-trigger
-                    // the merge process.
-                    if (getDriver(baseDriverName) != null
-                            && pipeconfs.containsKey(pipeconfId)) {
-                        mergedDriverName(baseDriverName, pipeconfId);
-                    }
-                });
+    private boolean driverExists(String name) {
+        return getDriver(name) != null;
     }
 
-    private void mergeAll(String baseDriverName) {
+    private void checkMissingMergedDriver(DeviceId deviceId) {
+        final PiPipeconfId pipeconfId = pipeconfMappingStore.getPipeconfId(deviceId);
+        final BasicDeviceConfig cfg = cfgService.getConfig(deviceId, BasicDeviceConfig.class);
+
+        if (pipeconfId == null) {
+            // No pipeconf associated.
+            return;
+        }
+
+        if (cfg == null || cfg.driver() == null) {
+            log.warn("Missing basic device config or driver key in netcfg for " +
+                             "{}, which is odd since it has a " +
+                             "pipeconf associated ({})",
+                     deviceId, pipeconfId);
+            return;
+        }
+
+        final String baseDriverName = cfg.driver();
+        final String mergedDriverName = mergedDriverName(baseDriverName, pipeconfId);
+
+        if (driverExists(mergedDriverName) ||
+                missingMergedDrivers.contains(mergedDriverName)) {
+            // Not missing, or already aware of it missing.
+            return;
+        }
+
+        log.info("Detected missing merged driver: {}", mergedDriverName);
+        missingMergedDrivers.add(mergedDriverName);
+        // Attempt building the driver now if all pieces are present.
+        // If not, either a driver or pipeconf event will re-trigger
+        // the process.
+        attemptDriverMerge(mergedDriverName);
+    }
+
+    private void attemptDriverMerge(String mergedDriverName) {
+        final String baseDriverName = getBaseDriverNameFromMerged(mergedDriverName);
+        final PiPipeconfId pipeconfId = getPipeconfIdFromMerged(mergedDriverName);
+        if (driverExists(baseDriverName) && pipeconfs.containsKey(pipeconfId)) {
+            doMergeDriver(baseDriverName, pipeconfId);
+        }
+    }
+
+    private void missingDriversWatchdogTask() {
+        while (true) {
+            // Most probably all missing drivers will be created before the
+            // watchdog interval, so wait before starting...
+            try {
+                TimeUnit.SECONDS.sleep(MISSING_DRIVER_WATCHDOG_INTERVAL);
+            } catch (InterruptedException e) {
+                log.warn("Interrupted! There are still {} missing merged drivers",
+                         missingMergedDrivers.size());
+            }
+            if (missingMergedDrivers.isEmpty()) {
+                log.info("There are no more missing merged drivers!");
+                return;
+            }
+            log.info("Detected {} missing merged drivers, attempt merge...",
+                     missingMergedDrivers.size());
+            missingMergedDrivers.forEach(this::attemptDriverMerge);
+        }
+    }
+
+    private void checkMissingMergedDrivers() {
+        cfgService.getSubjects(DeviceId.class, BasicDeviceConfig.class)
+                .forEach(this::checkMissingMergedDriver);
+    }
+
+    private void attemptMergeAll(String baseDriverName) {
         missingMergedDrivers.stream()
-                .filter(driverName -> {
-                    final String xx = getBaseDriverNameFromMerged(driverName);
+                .filter(missingDriver -> {
+                    // Filter missing merged drivers using this base driver.
+                    final String xx = getBaseDriverNameFromMerged(missingDriver);
                     return xx != null && xx.equals(baseDriverName);
                 })
-                .forEach(driverName -> {
-                    final PiPipeconfId pipeconfId = getPipeconfIdFromMerged(driverName);
-                    if (pipeconfs.containsKey(pipeconfId)) {
-                        doMergeDriver(baseDriverName, pipeconfId);
-                    }
-                });
+                .forEach(this::attemptDriverMerge);
     }
 
-    private void mergeAll(PiPipeconfId pipeconfId) {
+    private void attemptMergeAll(PiPipeconfId pipeconfId) {
         missingMergedDrivers.stream()
-                .filter(driverName -> {
-                    final PiPipeconfId xx = getPipeconfIdFromMerged(driverName);
+                .filter(missingDriver -> {
+                    // Filter missing merged drivers using this pipeconf.
+                    final PiPipeconfId xx = getPipeconfIdFromMerged(missingDriver);
                     return xx != null && xx.equals(pipeconfId);
                 })
-                .forEach(driverName -> {
-                    final String baseDriverName = getBaseDriverNameFromMerged(driverName);
-                    if (getDriver(baseDriverName) != null) {
-                        doMergeDriver(baseDriverName, pipeconfId);
-                    }
-                });
+                .forEach(this::attemptDriverMerge);
     }
 
     private class InternalDriverListener implements DriverListener {
 
         @Override
         public void event(DriverEvent event) {
-            executor.execute(() -> mergeAll(event.subject().name()));
+            executor.execute(() -> attemptMergeAll(event.subject().name()));
         }
 
         @Override