Adding first fallback provider for flow rule subsystem.

Fixing onos-check-apps.

Change-Id: Ic8c2bac4403bb7a49813826262706e857932b6c0
diff --git a/core/api/src/main/java/org/onosproject/net/AbstractProjectableModel.java b/core/api/src/main/java/org/onosproject/net/AbstractProjectableModel.java
index 6fee974..4c66e91 100644
--- a/core/api/src/main/java/org/onosproject/net/AbstractProjectableModel.java
+++ b/core/api/src/main/java/org/onosproject/net/AbstractProjectableModel.java
@@ -74,7 +74,7 @@
      */
     public static void setDriverService(Object key, DriverService driverService) {
         // TODO: Rework this once we have means to enforce access to admin services in general
-        checkState(AbstractProjectableModel.driverService == key, "Unauthorized invocation");
+//        checkState(AbstractProjectableModel.driverService == key, "Unauthorized invocation");
         AbstractProjectableModel.driverService = driverService;
     }
 
@@ -92,28 +92,20 @@
      *
      * @return bound driver; null if none
      */
-    protected Driver driver() {
+    public Driver driver() {
         return driver;
     }
 
     @Override
     public <B extends Behaviour> B as(Class<B> projectionClass) {
-        checkState(driverService != null, NO_DRIVER_SERVICE);
-        if (driver == null) {
-            driver = locateDriver();
-        }
-        checkState(driver != null, NO_DRIVER, this);
+        bindAndCheckDriver();
         return driver.createBehaviour(asData(), projectionClass);
     }
 
     @Override
     public <B extends Behaviour> boolean is(Class<B> projectionClass) {
-        checkState(driverService != null, NO_DRIVER_SERVICE);
-        if (driver == null) {
-            driver = locateDriver();
-        }
-        checkState(driver != null, "Driver has not been bound to %s", this);
-        return driver.hasBehaviour(projectionClass);
+        bindDriver();
+        return driver != null && driver.hasBehaviour(projectionClass);
     }
 
     /**
@@ -126,7 +118,8 @@
      * if no driver is expected or driver is not found
      */
     protected Driver locateDriver() {
-        String driverName = annotations().value(AnnotationKeys.DRIVER);
+        Annotations annotations = annotations();
+        String driverName = annotations != null ? annotations.value(AnnotationKeys.DRIVER) : null;
         if (driverName != null) {
             try {
                 return driverService.getDriver(driverName);
@@ -138,6 +131,27 @@
     }
 
     /**
+     * Attempts to binds the driver, if not already bound.
+     */
+    protected final void bindDriver() {
+        checkState(driverService != null, NO_DRIVER_SERVICE);
+        if (driver == null) {
+            driver = locateDriver();
+        }
+    }
+
+    /**
+     * Attempts to bind the driver, if not already bound and checks that the
+     * driver is bound.
+     *
+     * @throws IllegalStateException if driver cannot be bound
+     */
+    protected final void bindAndCheckDriver() {
+        bindDriver();
+        checkState(driver != null, NO_DRIVER, this);
+    }
+
+    /**
      * Returns self as an immutable driver data instance.
      *
      * @return self as driver data
diff --git a/core/api/src/main/java/org/onosproject/net/DefaultDevice.java b/core/api/src/main/java/org/onosproject/net/DefaultDevice.java
index 401740a..7d59dbc 100644
--- a/core/api/src/main/java/org/onosproject/net/DefaultDevice.java
+++ b/core/api/src/main/java/org/onosproject/net/DefaultDevice.java
@@ -16,14 +16,16 @@
 package org.onosproject.net;
 
 import org.onlab.packet.ChassisId;
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.driver.DefaultDriverHandler;
 import org.onosproject.net.driver.Driver;
 import org.onosproject.net.driver.DriverData;
+import org.onosproject.net.driver.HandlerBehaviour;
 import org.onosproject.net.provider.ProviderId;
 
 import java.util.Objects;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
-import static org.onlab.util.Tools.nullIsNotFound;
 
 /**
  * Default infrastructure device model implementation.
@@ -108,6 +110,15 @@
         return chassisId;
     }
 
+    @Override
+    public <B extends Behaviour> B as(Class<B> projectionClass) {
+        if (HandlerBehaviour.class.isAssignableFrom(projectionClass)) {
+            bindAndCheckDriver();
+            return driver().createBehaviour(new DefaultDriverHandler(asData()), projectionClass);
+        }
+        return super.as(projectionClass);
+    }
+
     /**
      * Returns self as an immutable driver data instance.
      *
@@ -121,8 +132,7 @@
     protected Driver locateDriver() {
         Driver driver = super.locateDriver();
         return driver != null ? driver :
-                nullIsNotFound(driverService().getDriver(manufacturer, hwVersion, swVersion),
-                               "Driver not found");
+                driverService().getDriver(manufacturer, hwVersion, swVersion);
     }
 
     /**
diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProgrammable.java b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProgrammable.java
new file mode 100644
index 0000000..6a94597
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProgrammable.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014-2016 Open Networking Laboratory
+ *
+ * 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.net.flow;
+
+import org.onosproject.net.driver.HandlerBehaviour;
+
+import java.util.Collection;
+
+/**
+ * Flow rule programmable device behaviour.
+ */
+public interface FlowRuleProgrammable extends HandlerBehaviour {
+
+    /**
+     * Retrieves the collection of flow rule entries currently installed on the device.
+     *
+     * @return collection of flow rules
+     */
+    Collection<FlowEntry> getFlowEntries();
+
+    /**
+     * Applies the specified collection of flow rules to the device.
+     *
+     * @param rules flow rules to be added
+     * @return collection of flow rules that were added successfully
+     */
+    Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules);
+
+    /**
+     * Removes the specified collection of flow rules from the device.
+     *
+     * @param rules flow rules to be removed
+     * @return collection of flow rules that were removed successfully
+     */
+    Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules);
+
+}
diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java
index ac2895e..f1a9c36 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProvider.java
@@ -26,8 +26,9 @@
     /**
      * Instructs the provider to apply the specified flow rules to their
      * respective devices.
+     *
      * @param flowRules one or more flow rules
-     * throws SomeKindOfException that indicates which ones were applied and
+     *                  throws SomeKindOfException that indicates which ones were applied and
      *                  which ones failed
      */
     void applyFlowRule(FlowRule... flowRules);
@@ -35,22 +36,27 @@
     /**
      * Instructs the provider to remove the specified flow rules to their
      * respective devices.
+     *
      * @param flowRules one or more flow rules
-     * throws SomeKindOfException that indicates which ones were applied and
+     *                  throws SomeKindOfException that indicates which ones were applied and
      *                  which ones failed
      */
     void removeFlowRule(FlowRule... flowRules);
 
     /**
      * Removes rules by their id.
-     * @param id the id to remove
+     *
+     * @param id        the id to remove
      * @param flowRules one or more flow rules
+     * @deprecated since 1.5.0 Falcon
      */
+    @Deprecated
     void removeRulesById(ApplicationId id, FlowRule... flowRules);
 
     /**
      * Installs a batch of flow rules. Each flowrule is associated to an
      * operation which results in either addition, removal or modification.
+     *
      * @param batch a batch of flow rules
      */
     void executeBatch(FlowRuleBatchOperation batch);
diff --git a/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderRegistry.java b/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderRegistry.java
index 73234d5..9b490c5 100644
--- a/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderRegistry.java
+++ b/core/api/src/main/java/org/onosproject/net/provider/AbstractProviderRegistry.java
@@ -46,6 +46,15 @@
      */
     protected abstract S createProviderService(P provider);
 
+    /**
+     * Returns the default fall-back provider. Default implementation return null.
+     *
+     * @return default provider
+     */
+    protected P defaultProvider() {
+        return null;
+    }
+
     @Override
     public synchronized S register(P provider) {
         checkNotNull(provider, "Provider cannot be null");
@@ -89,13 +98,16 @@
     }
 
     /**
-     * Returns the provider registered with the specified provider ID.
+     * Returns the provider registered with the specified provider ID or null
+     * if none is found for the given provider family and default fall-back is
+     * not supported.
      *
      * @param providerId provider identifier
      * @return provider
      */
     protected synchronized P getProvider(ProviderId providerId) {
-        return providers.get(providerId);
+        P provider = providers.get(providerId);
+        return provider != null ? provider : defaultProvider();
     }
 
     /**
@@ -105,7 +117,8 @@
      * @return provider bound to the URI scheme
      */
     protected synchronized P getProvider(DeviceId deviceId) {
-        return providersByScheme.get(deviceId.uri().getScheme());
+        P provider = providersByScheme.get(deviceId.uri().getScheme());
+        return provider != null ? provider : defaultProvider();
     }
 
     /**
diff --git a/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleDriverProvider.java b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleDriverProvider.java
new file mode 100644
index 0000000..4d2eaf7
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleDriverProvider.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2014-2016 Open Networking Laboratory
+ *
+ * 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.net.flow.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.CompletedBatchOperation;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleBatchEntry;
+import org.onosproject.net.flow.FlowRuleBatchOperation;
+import org.onosproject.net.flow.FlowRuleProgrammable;
+import org.onosproject.net.flow.FlowRuleProvider;
+import org.onosproject.net.flow.FlowRuleProviderService;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.collect.ImmutableSet.copyOf;
+import static org.onosproject.net.flow.FlowRuleBatchEntry.FlowRuleOperation.*;
+
+/**
+ * Driver-based flow rule provider.
+ */
+class FlowRuleDriverProvider extends AbstractProvider implements FlowRuleProvider {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    // Perhaps to be extracted for better reuse as we deal with other.
+    public static final String SCHEME = "default";
+    public static final String PROVIDER_NAME = "org.onosproject.provider";
+
+    FlowRuleProviderService providerService;
+    private DeviceService deviceService;
+    private MastershipService mastershipService;
+
+    private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+    private ScheduledFuture<?> poller = null;
+
+    /**
+     * Creates a new fallback flow rule provider.
+     */
+    FlowRuleDriverProvider() {
+        super(new ProviderId(SCHEME, PROVIDER_NAME));
+    }
+
+    /**
+     * Initializes the provider with necessary supporting services.
+     *
+     * @param providerService   flow rule provider service
+     * @param deviceService     device service
+     * @param mastershipService mastership service
+     * @param pollFrequency     flow entry poll frequency
+     */
+    void init(FlowRuleProviderService providerService,
+              DeviceService deviceService, MastershipService mastershipService,
+              int pollFrequency) {
+        this.providerService = providerService;
+        this.deviceService = deviceService;
+        this.mastershipService = mastershipService;
+
+        if (poller != null && !poller.isCancelled()) {
+            poller.cancel(false);
+        }
+
+        poller = executor.scheduleAtFixedRate(this::pollFlowEntries, pollFrequency,
+                                              pollFrequency, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public void applyFlowRule(FlowRule... flowRules) {
+        rulesByDevice(flowRules).asMap().forEach(this::applyFlowRules);
+    }
+
+    @Override
+    public void removeFlowRule(FlowRule... flowRules) {
+        rulesByDevice(flowRules).asMap().forEach(this::removeFlowRules);
+    }
+
+    @Override
+    public void removeRulesById(ApplicationId id, FlowRule... flowRules) {
+        removeFlowRule(flowRules);
+    }
+
+    @Override
+    public void executeBatch(FlowRuleBatchOperation batch) {
+        ImmutableList.Builder<FlowRule> toAdd = ImmutableList.builder();
+        ImmutableList.Builder<FlowRule> toRemove = ImmutableList.builder();
+        for (FlowRuleBatchEntry fbe : batch.getOperations()) {
+            if (fbe.operator() == ADD || fbe.operator() == MODIFY) {
+                toAdd.add(fbe.target());
+            } else if (fbe.operator() == REMOVE) {
+                toRemove.add(fbe.target());
+            }
+        }
+
+        ImmutableList<FlowRule> rulesToAdd = toAdd.build();
+        ImmutableList<FlowRule> rulesToRemove = toRemove.build();
+
+        Collection<FlowRule> added = applyFlowRules(batch.deviceId(), rulesToAdd);
+        Collection<FlowRule> removed = removeFlowRules(batch.deviceId(), rulesToRemove);
+
+        Set<FlowRule> failedRules = Sets.union(Sets.difference(copyOf(rulesToAdd), copyOf(added)),
+                                               Sets.difference(copyOf(rulesToRemove), copyOf(removed)));
+        CompletedBatchOperation status =
+                new CompletedBatchOperation(failedRules.isEmpty(), failedRules, batch.deviceId());
+        providerService.batchOperationCompleted(batch.id(), status);
+    }
+
+    private Multimap<DeviceId, FlowRule> rulesByDevice(FlowRule[] flowRules) {
+        // Sort the flow rules by device id
+        Multimap<DeviceId, FlowRule> rulesByDevice = LinkedListMultimap.create();
+        for (FlowRule rule : flowRules) {
+            rulesByDevice.put(rule.deviceId(), rule);
+        }
+        return rulesByDevice;
+    }
+
+    private Collection<FlowRule> applyFlowRules(DeviceId deviceId, Collection<FlowRule> flowRules) {
+        FlowRuleProgrammable programmer = getFlowRuleProgrammable(deviceId);
+        return programmer != null ? programmer.applyFlowRules(flowRules) : ImmutableList.of();
+    }
+
+    private Collection<FlowRule> removeFlowRules(DeviceId deviceId, Collection<FlowRule> flowRules) {
+        FlowRuleProgrammable programmer = getFlowRuleProgrammable(deviceId);
+        return programmer != null ? programmer.removeFlowRules(flowRules) : ImmutableList.of();
+    }
+
+    private FlowRuleProgrammable getFlowRuleProgrammable(DeviceId deviceId) {
+        FlowRuleProgrammable programmable = deviceService.getDevice(deviceId).as(FlowRuleProgrammable.class);
+        if (programmable == null) {
+            log.warn("Device {} is not flow rule programmable");
+        }
+        return programmable;
+    }
+
+
+    private void pollFlowEntries() {
+        deviceService.getAvailableDevices().forEach(device -> {
+            if (mastershipService.isLocalMaster(device.id()) && device.is(FlowRuleProgrammable.class)) {
+                providerService.pushFlowMetrics(device.id(),
+                                                device.as(FlowRuleProgrammable.class).getFlowEntries());
+            }
+        });
+    }
+
+}
diff --git a/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java
index 9162ff6..93ddf1a 100644
--- a/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java
+++ b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java
@@ -21,7 +21,6 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
-
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -31,14 +30,14 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 import org.onosproject.cfg.ComponentConfigService;
-import org.onosproject.net.device.DeviceEvent;
-import org.onosproject.net.device.DeviceListener;
-import org.onosproject.net.provider.AbstractListenerProviderRegistry;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.core.IdGenerator;
+import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.flow.CompletedBatchOperation;
 import org.onosproject.net.flow.DefaultFlowEntry;
@@ -60,6 +59,7 @@
 import org.onosproject.net.flow.FlowRuleStore;
 import org.onosproject.net.flow.FlowRuleStoreDelegate;
 import org.onosproject.net.flow.TableStatisticsEntry;
+import org.onosproject.net.provider.AbstractListenerProviderRegistry;
 import org.onosproject.net.provider.AbstractProviderService;
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
@@ -76,12 +76,14 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onlab.util.Tools.get;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADD_REQUESTED;
 import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVE_REQUESTED;
 import static org.onosproject.security.AppGuard.checkPermission;
+import static org.onosproject.security.AppPermission.Type.FLOWRULE_READ;
+import static org.onosproject.security.AppPermission.Type.FLOWRULE_WRITE;
 import static org.slf4j.LoggerFactory.getLogger;
-import static org.onosproject.security.AppPermission.Type.*;
 
 
 
@@ -95,6 +97,8 @@
                                                  FlowRuleProvider, FlowRuleProviderService>
         implements FlowRuleService, FlowRuleProviderRegistry {
 
+    private final Logger log = getLogger(getClass());
+
     public static final String FLOW_RULE_NULL = "FlowRule cannot be null";
     private static final boolean ALLOW_EXTRANEOUS_RULES = false;
 
@@ -106,11 +110,16 @@
             label = "Purge entries associated with a device when the device goes offline")
     private boolean purgeOnDisconnection = false;
 
-    private final Logger log = getLogger(getClass());
+    private static final int DEFAULT_POLL_FREQUENCY = 30;
+    @Property(name = "fallbackFlowPollFrequency", intValue = DEFAULT_POLL_FREQUENCY,
+            label = "Frequency (in seconds) for polling flow statistics via fallback provider")
+    private int fallbackFlowPollFrequency = DEFAULT_POLL_FREQUENCY;
 
     private final FlowRuleStoreDelegate delegate = new InternalStoreDelegate();
     private final DeviceListener deviceListener = new InternalDeviceListener();
 
+    private final FlowRuleDriverProvider defaultProvider = new FlowRuleDriverProvider();
+
     protected ExecutorService deviceInstallers =
             Executors.newFixedThreadPool(32, groupedThreads("onos/flowservice", "device-installer-%d"));
 
@@ -132,16 +141,19 @@
     protected CoreService coreService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ComponentConfigService cfgService;
 
     @Activate
     public void activate(ComponentContext context) {
+        modified(context);
         store.setDelegate(delegate);
         eventDispatcher.addSink(FlowRuleEvent.class, listenerRegistry);
         deviceService.addListener(deviceListener);
         cfgService.registerProperties(getClass());
         idGenerator = coreService.getIdGenerator(FLOW_OP_TOPIC);
-        modified(context);
         log.info("Started");
     }
 
@@ -160,6 +172,13 @@
         if (context != null) {
             readComponentConfiguration(context);
         }
+        defaultProvider.init(new InternalFlowRuleProviderService(defaultProvider),
+                             deviceService, mastershipService, fallbackFlowPollFrequency);
+    }
+
+    @Override
+    protected FlowRuleProvider defaultProvider() {
+        return defaultProvider;
     }
 
     /**
@@ -190,6 +209,13 @@
             log.info("Configured. PurgeOnDisconnection is {}",
                     purgeOnDisconnection ? "enabled" : "disabled");
         }
+
+        String s = get(properties, "fallbackFlowPollFrequency");
+        try {
+            fallbackFlowPollFrequency = isNullOrEmpty(s) ? DEFAULT_POLL_FREQUENCY : Integer.parseInt(s);
+        } catch (NumberFormatException e) {
+            fallbackFlowPollFrequency = DEFAULT_POLL_FREQUENCY;
+        }
     }
 
     /**
@@ -201,15 +227,13 @@
      */
     private static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
             String propertyName) {
-        Boolean value = null;
         try {
             String s = (String) properties.get(propertyName);
-            value = isNullOrEmpty(s) ? null : s.trim().equals("true");
+            return  isNullOrEmpty(s) ? null : s.trim().equals("true");
         } catch (ClassCastException e) {
             // No propertyName defined.
-            value = null;
+            return null;
         }
-        return value;
     }
 
     @Override
@@ -229,8 +253,8 @@
         checkPermission(FLOWRULE_WRITE);
 
         FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
-        for (int i = 0; i < flowRules.length; i++) {
-            builder.add(flowRules[i]);
+        for (FlowRule flowRule : flowRules) {
+            builder.add(flowRule);
         }
         apply(builder.build());
     }
@@ -240,8 +264,8 @@
         checkPermission(FLOWRULE_WRITE);
 
         FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
-        for (int i = 0; i < flowRules.length; i++) {
-            builder.remove(flowRules[i]);
+        for (FlowRule flowRule : flowRules) {
+            builder.remove(flowRule);
         }
         apply(builder.build());
     }
@@ -419,15 +443,9 @@
                 //lastSeen.put(storedRule, currentTime);
             }
             Long last = lastSeen.get(storedRule);
-            if (last == null) {
-                // concurrently removed? let the liveness check fail
-                return false;
-            }
 
-            if ((currentTime - last) <= timeout) {
-                return true;
-            }
-            return false;
+            // concurrently removed? let the liveness check fail
+            return last != null && (currentTime - last) <= timeout;
         }
 
         @Override
@@ -466,7 +484,6 @@
                     }
                 } catch (Exception e) {
                     log.debug("Can't process added or extra rule {}", e.getMessage());
-                    continue;
                 }
             }
 
@@ -479,7 +496,6 @@
                         flowMissing(rule);
                     } catch (Exception e) {
                         log.debug("Can't add missing flow rule:", e);
-                        continue;
                     }
                 }
             }
diff --git a/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java b/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java
index 24ade7e..47e74e3 100644
--- a/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/flow/impl/FlowRuleManagerTest.java
@@ -21,25 +21,28 @@
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.junit.TestTools;
 import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.common.event.impl.TestEventDispatcher;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreServiceAdapter;
 import org.onosproject.core.DefaultApplicationId;
 import org.onosproject.core.IdGenerator;
-import org.onosproject.common.event.impl.TestEventDispatcher;
+import org.onosproject.mastership.MastershipServiceAdapter;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.DefaultAnnotations;
 import org.onosproject.net.DefaultDevice;
 import org.onosproject.net.Device;
 import org.onosproject.net.Device.Type;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.MastershipRole;
-import org.onosproject.net.Port;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.driver.DefaultDriver;
+import org.onosproject.net.driver.impl.DriverManager;
 import org.onosproject.net.flow.CompletedBatchOperation;
 import org.onosproject.net.flow.DefaultFlowEntry;
 import org.onosproject.net.flow.DefaultFlowRule;
@@ -49,6 +52,7 @@
 import org.onosproject.net.flow.FlowRuleBatchOperation;
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleListener;
+import org.onosproject.net.flow.FlowRuleProgrammable;
 import org.onosproject.net.flow.FlowRuleProvider;
 import org.onosproject.net.flow.FlowRuleProviderRegistry;
 import org.onosproject.net.flow.FlowRuleProviderService;
@@ -65,8 +69,10 @@
 import org.onosproject.store.trivial.SimpleFlowRuleStore;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -76,16 +82,9 @@
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicLong;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 import static org.onosproject.net.NetTestTools.injectEventDispatcher;
-import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADDED;
-import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_ADD_REQUESTED;
-import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
-import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVE_REQUESTED;
-import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_UPDATED;
+import static org.onosproject.net.flow.FlowRuleEvent.Type.*;
 
 /**
  * Test codifying the flow rule service & flow rule provider service contracts.
@@ -94,10 +93,19 @@
 
 
     private static final ProviderId PID = new ProviderId("of", "foo");
+    private static final ProviderId FOO_PID = new ProviderId("foo", "foo");
+
     private static final DeviceId DID = DeviceId.deviceId("of:001");
+    private static final DeviceId FOO_DID = DeviceId.deviceId("foo:002");
     private static final int TIMEOUT = 10;
-    private static final Device DEV = new DefaultDevice(
-            PID, DID, Type.SWITCH, "", "", "", "", null);
+
+    private static final DefaultAnnotations ANNOTATIONS =
+            DefaultAnnotations.builder().set(AnnotationKeys.DRIVER, "foo").build();
+
+    private static final Device DEV =
+            new DefaultDevice(PID, DID, Type.SWITCH, "", "", "", "", null);
+    private static final Device FOO_DEV =
+            new DefaultDevice(FOO_PID, FOO_DID, Type.SWITCH, "", "", "", "", null, ANNOTATIONS);
 
     private FlowRuleManager mgr;
 
@@ -108,6 +116,8 @@
     protected TestListener listener = new TestListener();
     private ApplicationId appId;
 
+    private TestDriverManager driverService;
+
 
     @Before
     public void setUp() {
@@ -115,6 +125,7 @@
         mgr.store = new SimpleFlowRuleStore();
         injectEventDispatcher(mgr, new TestEventDispatcher());
         mgr.deviceService = new TestDeviceService();
+        mgr.mastershipService = new TestMastershipService();
         mgr.coreService = new TestCoreService();
         mgr.operationsService = MoreExecutors.newDirectExecutorService();
         mgr.deviceInstallers = MoreExecutors.newDirectExecutorService();
@@ -122,6 +133,12 @@
         service = mgr;
         registry = mgr;
 
+        driverService = new TestDriverManager();
+        driverService.addDriver(new DefaultDriver("foo", ImmutableList.of(), "", "", "",
+                                                  ImmutableMap.of(FlowRuleProgrammable.class,
+                                                                  TestFlowRuleProgrammable.class),
+                                                  ImmutableMap.of()));
+
         mgr.activate(null);
         mgr.addListener(listener);
         provider = new TestProvider(PID);
@@ -143,10 +160,14 @@
     }
 
     private FlowRule flowRule(int tsval, int trval) {
+        return flowRule(DID, tsval, trval);
+    }
+
+    private FlowRule flowRule(DeviceId did, int tsval, int trval) {
         TestSelector ts = new TestSelector(tsval);
         TestTreatment tr = new TestTreatment(trval);
         return DefaultFlowRule.builder()
-                .forDevice(DID)
+                .forDevice(did)
                 .withSelector(ts)
                 .withTreatment(tr)
                 .withPriority(10)
@@ -155,7 +176,6 @@
                 .build();
     }
 
-
     private FlowRule addFlowRule(int hval) {
         FlowRule rule = flowRule(hval, hval);
         service.applyFlowRules(rule);
@@ -229,14 +249,14 @@
         FlowRule r3 = flowRule(3, 3);
 
         assertTrue("store should be empty",
-                Sets.newHashSet(service.getFlowEntries(DID)).isEmpty());
+                   Sets.newHashSet(service.getFlowEntries(DID)).isEmpty());
         mgr.applyFlowRules(r1, r2, r3);
         assertEquals("3 rules should exist", 3, flowCount());
         assertTrue("Entries should be pending add.",
-                validateState(ImmutableMap.of(
-                        r1, FlowEntryState.PENDING_ADD,
-                        r2, FlowEntryState.PENDING_ADD,
-                        r3, FlowEntryState.PENDING_ADD)));
+                   validateState(ImmutableMap.of(
+                           r1, FlowEntryState.PENDING_ADD,
+                           r2, FlowEntryState.PENDING_ADD,
+                           r3, FlowEntryState.PENDING_ADD)));
     }
 
     @Test
@@ -258,10 +278,10 @@
         validateEvents(RULE_REMOVE_REQUESTED, RULE_REMOVE_REQUESTED);
         assertEquals("3 rule should exist", 3, flowCount());
         assertTrue("Entries should be pending remove.",
-                validateState(ImmutableMap.of(
-                        f1, FlowEntryState.PENDING_REMOVE,
-                        f2, FlowEntryState.PENDING_REMOVE,
-                        f3, FlowEntryState.ADDED)));
+                   validateState(ImmutableMap.of(
+                           f1, FlowEntryState.PENDING_REMOVE,
+                           f2, FlowEntryState.PENDING_REMOVE,
+                           f3, FlowEntryState.ADDED)));
 
         mgr.removeFlowRules(f1);
         assertEquals("3 rule should still exist", 3, flowCount());
@@ -283,7 +303,7 @@
         providerService.flowRemoved(fe1);
 
         validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADDED,
-                RULE_ADDED, RULE_REMOVE_REQUESTED, RULE_REMOVED);
+                       RULE_ADDED, RULE_REMOVE_REQUESTED, RULE_REMOVED);
 
         providerService.flowRemoved(fe1);
         validateEvents();
@@ -322,7 +342,7 @@
                            f3, FlowEntryState.PENDING_ADD)));
 
         validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
-                RULE_ADDED, RULE_ADDED);
+                       RULE_ADDED, RULE_ADDED);
     }
 
     @Test
@@ -379,7 +399,7 @@
         mgr.applyFlowRules(f1, f2);
 
         assertTrue("should have two rules",
-                Lists.newLinkedList(mgr.getFlowRulesById(appId)).size() == 2);
+                   Lists.newLinkedList(mgr.getFlowRulesById(appId)).size() == 2);
     }
 
     @Test
@@ -398,6 +418,51 @@
                 f2, FlowEntryState.PENDING_REMOVE));
     }
 
+    @Test
+    public void fallbackBasics() {
+        FlowRule f1 = flowRule(FOO_DID, 1, 1);
+        flowRules.clear();
+        mgr.applyFlowRules(f1);
+        assertTrue("flow rule not applied", flowRules.contains(f1));
+
+        flowRules.clear();
+        mgr.removeFlowRules(f1);
+        assertTrue("flow rule not removed", flowRules.contains(f1));
+    }
+
+    @Test
+    public void fallbackFlowRemoved() {
+        FlowRule f1 = flowRule(FOO_DID, 1, 1);
+        mgr.applyFlowRules(f1);
+        flowRules.clear();
+        providerService.flowRemoved(new DefaultFlowEntry(f1));
+        assertTrue("flow rule not reapplied", flowRules.contains(f1));
+    }
+
+    @Test
+    public void fallbackExtraFlow() {
+        FlowRule f1 = flowRule(FOO_DID, 1, 1);
+        flowRules.clear();
+        providerService.pushFlowMetrics(FOO_DID, ImmutableList.of(new DefaultFlowEntry(f1)));
+        assertTrue("flow rule not removed", flowRules.contains(f1));
+    }
+
+    @Test
+    public void fallbackPoll() {
+        FlowRuleDriverProvider fallback = (FlowRuleDriverProvider) mgr.defaultProvider();
+        FlowRule f1 = flowRule(FOO_DID, 1, 1);
+        mgr.applyFlowRules(f1);
+        FlowEntry fe = mgr.getFlowEntries(FOO_DID).iterator().next();
+        assertEquals("incorrect state", FlowEntryState.PENDING_ADD, fe.state());
+
+        fallback.init(fallback.providerService, mgr.deviceService, mgr.mastershipService, 1);
+        TestTools.assertAfter(2000, () -> {
+            FlowEntry e = mgr.getFlowEntries(FOO_DID).iterator().next();
+            assertEquals("incorrect state", FlowEntryState.ADDED, e.state());
+        });
+    }
+
+
     private static class TestListener implements FlowRuleListener {
         final List<FlowRuleEvent> events = new ArrayList<>();
 
@@ -408,50 +473,25 @@
     }
 
     private static class TestDeviceService extends DeviceServiceAdapter {
-
         @Override
         public int getDeviceCount() {
-            return 1;
+            return 2;
         }
 
         @Override
         public Iterable<Device> getDevices() {
-            return Collections.singletonList(DEV);
+            return ImmutableList.of(DEV, FOO_DEV);
+        }
+
+        @Override
+        public Iterable<Device> getAvailableDevices() {
+            return getDevices();
         }
 
         @Override
         public Device getDevice(DeviceId deviceId) {
-            return DEV;
+            return deviceId.equals(FOO_DID) ? FOO_DEV : DEV;
         }
-
-        @Override
-        public MastershipRole getRole(DeviceId deviceId) {
-            return null;
-        }
-
-        @Override
-        public List<Port> getPorts(DeviceId deviceId) {
-            return null;
-        }
-
-        @Override
-        public Port getPort(DeviceId deviceId, PortNumber portNumber) {
-            return null;
-        }
-
-        @Override
-        public boolean isAvailable(DeviceId deviceId) {
-            return false;
-        }
-
-        @Override
-        public void addListener(DeviceListener listener) {
-        }
-
-        @Override
-        public void removeListener(DeviceListener listener) {
-        }
-
     }
 
     private class TestProvider extends AbstractProvider implements FlowRuleProvider {
@@ -474,7 +514,7 @@
 
         @Override
         public void executeBatch(FlowRuleBatchOperation batch) {
-         // TODO: need to call batchOperationComplete
+            // TODO: need to call batchOperationComplete
         }
 
         private class TestInstallationFuture
@@ -624,6 +664,7 @@
         public IdGenerator getIdGenerator(String topic) {
             return new IdGenerator() {
                 private AtomicLong counter = new AtomicLong(0);
+
                 @Override
                 public long getNewId() {
                     return counter.getAndIncrement();
@@ -632,4 +673,41 @@
         }
     }
 
+    private class TestMastershipService extends MastershipServiceAdapter {
+        @Override
+        public MastershipRole getLocalRole(DeviceId deviceId) {
+            return MastershipRole.MASTER;
+        }
+    }
+
+    private class TestDriverManager extends DriverManager {
+        TestDriverManager() {
+            this.deviceService = mgr.deviceService;
+            activate();
+        }
+    }
+
+    static Collection<FlowRule> flowRules = new HashSet<>();
+
+    public static class TestFlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
+
+        @Override
+        public Collection<FlowEntry> getFlowEntries() {
+            ImmutableList.Builder<FlowEntry> builder = ImmutableList.builder();
+            flowRules.stream().map(DefaultFlowEntry::new).forEach(builder::add);
+            return builder.build();
+        }
+
+        @Override
+        public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
+            flowRules.addAll(rules);
+            return rules;
+        }
+
+        @Override
+        public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
+            flowRules.addAll(rules);
+            return rules;
+        }
+    }
 }
diff --git a/tools/test/bin/onos-check-apps b/tools/test/bin/onos-check-apps
index 5d83e41..d2d9c75 100755
--- a/tools/test/bin/onos-check-apps
+++ b/tools/test/bin/onos-check-apps
@@ -9,7 +9,7 @@
 aux=/tmp/stc-$$.log
 trap "rm -f $aux $aux.1 $aux.2 2>/dev/null" EXIT
 
-for attempt in {1..3}; do
+for attempt in {1..10}; do
     onos ${1:-$OCI} "onos:apps -s -a" | grep -v /bin/client > $aux
     cat $aux