[ONOS-6986] Implement getGroups in GroupProgrammable

Change-Id: I9f25bddb6a8baad74e8e74abd44187a9c3f6520a
diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupProgrammable.java b/core/api/src/main/java/org/onosproject/net/group/GroupProgrammable.java
index 178a288..5be93d2 100644
--- a/core/api/src/main/java/org/onosproject/net/group/GroupProgrammable.java
+++ b/core/api/src/main/java/org/onosproject/net/group/GroupProgrammable.java
@@ -16,9 +16,12 @@
 
 package org.onosproject.net.group;
 
+import com.google.common.collect.ImmutableList;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.driver.HandlerBehaviour;
 
+import java.util.Collection;
+
 /**
  * Group programmable device behaviour.
  */
@@ -31,4 +34,13 @@
      * @param groupOps operations to be performed
      */
     void performGroupOperation(DeviceId deviceId, GroupOperations groupOps);
+
+    /**
+     * Queries the groups from the device.
+     *
+     * @return collection of groups
+     */
+    default Collection<Group> getGroups() {
+        return ImmutableList.of();
+    }
 }
diff --git a/core/net/src/main/java/org/onosproject/net/group/impl/GroupDriverProvider.java b/core/net/src/main/java/org/onosproject/net/group/impl/GroupDriverProvider.java
index 414d9e2..6eea583 100644
--- a/core/net/src/main/java/org/onosproject/net/group/impl/GroupDriverProvider.java
+++ b/core/net/src/main/java/org/onosproject/net/group/impl/GroupDriverProvider.java
@@ -16,17 +16,34 @@
 
 package org.onosproject.net.group.impl;
 
+import com.google.common.collect.Sets;
+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.group.Group;
 import org.onosproject.net.group.GroupOperations;
 import org.onosproject.net.group.GroupProgrammable;
 import org.onosproject.net.group.GroupProvider;
+import org.onosproject.net.group.GroupProviderService;
 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.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED;
+
 /**
  * Driver-based Group rule provider.
  */
@@ -37,19 +54,49 @@
     // To be extracted for reuse as we deal with other.
     private static final String SCHEME = "default";
     private static final String PROVIDER_NAME = "org.onosproject.provider";
+
+    // potentially positive device event
+    private static final Set<DeviceEvent.Type> POSITIVE_DEVICE_EVENT =
+            Sets.immutableEnumSet(DEVICE_ADDED,
+                    DEVICE_AVAILABILITY_CHANGED);
+
     protected DeviceService deviceService;
+    protected GroupProviderService groupProviderService;
+    protected MastershipService mastershipService;
+
+    private InternalDeviceListener deviceListener = new InternalDeviceListener();
+    private ScheduledExecutorService executor
+            = newSingleThreadScheduledExecutor(groupedThreads("GroupDriverProvider", "%d", log));
+    private ScheduledFuture<?> poller = null;
 
     public GroupDriverProvider() {
         super(new ProviderId(SCHEME, PROVIDER_NAME));
     }
 
     /**
-     * Initializes the provider with the necessary device service.
+     * Initializes the provider with the necessary device service, group provider service,
+     * mastership service and poll frequency.
      *
-     * @param deviceService device service
+     * @param deviceService        device service
+     * @param groupProviderService group provider service
+     * @param mastershipService    mastership service
+     * @param pollFrequency        group entry poll frequency
      */
-    void init(DeviceService deviceService) {
+    void init(DeviceService deviceService, GroupProviderService groupProviderService,
+              MastershipService mastershipService, int pollFrequency) {
         this.deviceService = deviceService;
+        this.groupProviderService = groupProviderService;
+        this.mastershipService = mastershipService;
+
+        deviceService.addListener(deviceListener);
+
+        if (poller != null && !poller.isCancelled()) {
+            poller.cancel(false);
+        }
+
+        poller = executor.scheduleAtFixedRate(this::pollGroups, pollFrequency,
+                pollFrequency, TimeUnit.SECONDS);
+
     }
 
     @Override
@@ -60,6 +107,20 @@
         }
     }
 
+    private void pollGroups() {
+        deviceService.getAvailableDevices().forEach(device -> {
+            if (mastershipService.isLocalMaster(device.id()) &&
+                    device.is(GroupProgrammable.class)) {
+                pollDeviceGroups(device.id());
+            }
+        });
+    }
+
+    private void pollDeviceGroups(DeviceId deviceId) {
+        Collection<Group> groups = getGroupProgrammable(deviceId).getGroups();
+        groupProviderService.pushGroupMetrics(deviceId, groups);
+    }
+
     private GroupProgrammable getGroupProgrammable(DeviceId deviceId) {
         Device device = deviceService.getDevice(deviceId);
         if (device.is(GroupProgrammable.class)) {
@@ -69,4 +130,29 @@
             return null;
         }
     }
+
+    private class InternalDeviceListener implements DeviceListener {
+
+        @Override
+        public void event(DeviceEvent event) {
+            executor.execute(() -> handleEvent(event));
+        }
+
+        @Override
+        public boolean isRelevant(DeviceEvent event) {
+            Device device = event.subject();
+            return POSITIVE_DEVICE_EVENT.contains(event.type()) &&
+                    device.is(GroupProgrammable.class);
+        }
+
+        private void handleEvent(DeviceEvent event) {
+            Device device = event.subject();
+            boolean isRelevant = mastershipService.isLocalMaster(device.id()) &&
+                    deviceService.isAvailable(device.id());
+
+            if (isRelevant) {
+                pollDeviceGroups(device.id());
+            }
+        }
+    }
 }
diff --git a/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java b/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java
index 31e95a4..bc3c64d 100644
--- a/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java
+++ b/core/net/src/main/java/org/onosproject/net/group/impl/GroupManager.java
@@ -26,6 +26,7 @@
 import org.onlab.util.Tools;
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.ApplicationId;
+import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
@@ -54,6 +55,8 @@
 import java.util.Collections;
 import java.util.Dictionary;
 
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onlab.util.Tools.get;
 import static org.onosproject.security.AppGuard.checkPermission;
 import static org.onosproject.security.AppPermission.Type.GROUP_READ;
 import static org.onosproject.security.AppPermission.Type.GROUP_WRITE;
@@ -76,6 +79,8 @@
     private final GroupStoreDelegate delegate = new InternalGroupStoreDelegate();
     private final DeviceListener deviceListener = new InternalDeviceListener();
 
+    private final  GroupDriverProvider defaultProvider = new GroupDriverProvider();
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected GroupStore store;
 
@@ -85,10 +90,18 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ComponentConfigService cfgService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    private static final int DEFAULT_POLL_FREQUENCY = 30;
+    @Property(name = "fallbackGroupPollFrequency", intValue = DEFAULT_POLL_FREQUENCY,
+            label = "Frequency (in seconds) for polling groups via fallback provider")
+    private int fallbackGroupPollFrequency = DEFAULT_POLL_FREQUENCY;
+
     @Property(name = "purgeOnDisconnection", boolValue = false,
             label = "Purge entries associated with a device when the device goes offline")
     private boolean purgeOnDisconnection = false;
-    private final  GroupDriverProvider defaultProvider = new GroupDriverProvider();
+
 
     @Activate
     public void activate(ComponentContext context) {
@@ -114,7 +127,8 @@
         if (context != null) {
             readComponentConfiguration(context);
         }
-        defaultProvider.init(deviceService);
+        defaultProvider.init(deviceService, new InternalGroupProviderService(defaultProvider),
+                mastershipService, fallbackGroupPollFrequency);
     }
 
     @Override
@@ -140,6 +154,12 @@
             log.info("Configured. PurgeOnDisconnection is {}",
                     purgeOnDisconnection ? "enabled" : "disabled");
         }
+        String s = get(properties, "fallbackGroupPollFrequency");
+        try {
+            fallbackGroupPollFrequency = isNullOrEmpty(s) ? DEFAULT_POLL_FREQUENCY : Integer.parseInt(s);
+        } catch (NumberFormatException e) {
+            fallbackGroupPollFrequency = DEFAULT_POLL_FREQUENCY;
+        }
     }
 
     /**
diff --git a/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java b/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java
index 2415eea..7135c49 100644
--- a/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/group/impl/GroupManagerTest.java
@@ -21,6 +21,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.junit.TestTools;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.MplsLabel;
 import org.onosproject.cfg.ComponentConfigAdapter;
@@ -28,11 +29,13 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.DefaultApplicationId;
 import org.onosproject.core.GroupId;
+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.DeviceId;
+import org.onosproject.net.MastershipRole;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceServiceAdapter;
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
@@ -67,6 +70,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
@@ -89,7 +93,7 @@
     private static final Device FOO_DEV =
             new DefaultDevice(FOO_PID, FOO_DID, Device.Type.SWITCH, "", "", "", "", null, ANNOTATIONS);
 
-    private GroupManager mgr;
+    private static GroupManager mgr;
     private GroupService groupService;
     private GroupProviderRegistry providerRegistry;
     private TestGroupListener internalListener = new TestGroupListener();
@@ -108,6 +112,7 @@
         mgr.deviceService = new TestDeviceService();
         mgr.cfgService = new ComponentConfigAdapter();
         mgr.store = new SimpleGroupStore();
+        mgr.mastershipService = new TestMastershipService();
         injectEventDispatcher(mgr, new TestEventDispatcher());
         providerRegistry = mgr;
 
@@ -302,6 +307,27 @@
         testRemoveGroup(FOO_DID);
     }
 
+    @Test
+    public void fallbackPoll() {
+        // Test Group creation before AUDIT process
+        testGroupCreationBeforeAudit(FOO_DID);
+        programmableTestCleanUp();
+
+        // Test audit with extraneous and missing groups
+        testAuditWithExtraneousMissingGroups(FOO_DID);
+
+        // Test audit with confirmed groups
+        Group createdGroup = testAuditWithConfirmedGroups(FOO_DID);
+        GroupDriverProvider fallback = (GroupDriverProvider) mgr.defaultProvider();
+
+        fallback.init(mgr.deviceService, fallback.groupProviderService, mgr.mastershipService, 1);
+
+        TestTools.assertAfter(2000, () -> {
+            Group e = mgr.getGroups(FOO_DID).iterator().next();
+            assertEquals("incorrect group", createdGroup, e);
+        });
+    }
+
     private void programmableTestCleanUp() {
         groupOperations.clear();
         lastDeviceIdProgrammable = null;
@@ -414,7 +440,7 @@
     }
 
     // Test AUDIT with confirmed groups
-    private void testAuditWithConfirmedGroups(DeviceId deviceId) {
+    private Group testAuditWithConfirmedGroups(DeviceId deviceId) {
         GroupKey key = new DefaultGroupKey("group1BeforeAudit".getBytes());
         Group createdGroup = groupService.getGroup(deviceId, key);
         createdGroup = new DefaultGroup(createdGroup.id(),
@@ -424,6 +450,7 @@
         List<Group> groupEntries = Collections.singletonList(createdGroup);
         providerService.pushGroupMetrics(deviceId, groupEntries);
         internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_ADDED));
+        return createdGroup;
     }
 
     // Test group add bucket operations
@@ -649,7 +676,7 @@
         internalListener.validateEvent(Collections.singletonList(GroupEvent.Type.GROUP_REMOVE_FAILED));
     }
 
-    private Group createSouthboundGroupEntry(GroupId gId,
+    private static Group createSouthboundGroupEntry(GroupId gId,
                                              List<PortNumber> ports,
                                              long referenceCount, DeviceId deviceId) {
         List<PortNumber> outPorts = new ArrayList<>();
@@ -749,6 +776,13 @@
         }
     }
 
+    private class TestMastershipService extends MastershipServiceAdapter {
+        @Override
+        public MastershipRole getLocalRole(DeviceId deviceId) {
+            return MastershipRole.MASTER;
+        }
+    }
+
     private class TestDriverManager extends DriverManager {
         TestDriverManager(DriverRegistry registry) {
             this.registry = registry;
@@ -766,6 +800,11 @@
             lastDeviceIdProgrammable = deviceId;
             groupOperations.addAll(groupOps.operations());
         }
+
+        @Override
+        public Collection<Group> getGroups() {
+            return ImmutableList.of(mgr.getGroups(FOO_DID).iterator().next());
+        }
     }
 
     public void validate(DeviceId expectedDeviceId,
@@ -783,5 +822,3 @@
         lastDeviceIdProgrammable = null;
     }
 }
-
-