Group event indicating failover of previously used live port

Change-Id: I32749b38d5e4fab93fa97bbf6587bd0dc91db88c
diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupEvent.java b/core/api/src/main/java/org/onosproject/net/group/GroupEvent.java
index 23aa97b..24d3221 100644
--- a/core/api/src/main/java/org/onosproject/net/group/GroupEvent.java
+++ b/core/api/src/main/java/org/onosproject/net/group/GroupEvent.java
@@ -56,6 +56,13 @@
          */
         GROUP_UPDATE_FAILED,
 
+        /**
+         * Signifies change in the first live bucket in failover group
+         * (i.e. change in which bucket is in use).
+         * Only to be used with failover Group.
+         */
+        GROUP_BUCKET_FAILOVER,
+
         // internal event between Manager <-> Store
 
         /*
diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupProviderService.java b/core/api/src/main/java/org/onosproject/net/group/GroupProviderService.java
index ce99680..0a826bb 100644
--- a/core/api/src/main/java/org/onosproject/net/group/GroupProviderService.java
+++ b/core/api/src/main/java/org/onosproject/net/group/GroupProviderService.java
@@ -41,7 +41,12 @@
      * @param deviceId device identifier
      * @param groupEntries collection of group entries as seen in data plane
      */
-    void pushGroupMetrics(DeviceId deviceId,
-                       Collection<Group> groupEntries);
+    void pushGroupMetrics(DeviceId deviceId, Collection<Group> groupEntries);
 
+    /**
+     * Notifies store of group failovers.
+     *
+     * @param failoverGroups failover groups in which a failover has occurred
+     */
+    void notifyOfFailovers(Collection<Group> failoverGroups);
 }
diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupStore.java b/core/api/src/main/java/org/onosproject/net/group/GroupStore.java
index 3b29d61..d2dd992 100644
--- a/core/api/src/main/java/org/onosproject/net/group/GroupStore.java
+++ b/core/api/src/main/java/org/onosproject/net/group/GroupStore.java
@@ -179,4 +179,9 @@
      * @param groupEntries the group entries as received from southbound
      */
     void pushGroupMetrics(DeviceId deviceId, Collection<Group> groupEntries);
+
+    /**
+     * Indicates failover within a failover group.
+     */
+    void notifyOfFailovers(Collection<Group> failoverGroups);
 }
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java
index 6e03b2b..9727c90 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStore.java
@@ -646,6 +646,17 @@
         }
     }
 
+    @Override
+    public void notifyOfFailovers(Collection<Group> failoverGroups) {
+        List<GroupEvent> failoverEvents = new ArrayList<>();
+        failoverGroups.forEach(group -> {
+            if (group.type() == Group.Type.FAILOVER) {
+                failoverEvents.add(new GroupEvent(GroupEvent.Type.GROUP_BUCKET_FAILOVER, group));
+            }
+        });
+        notifyDelegate(failoverEvents);
+    }
+
     private void groupMissing(Group group) {
         switch (group.state()) {
             case PENDING_DELETE:
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 1150d05..2e4a375 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
@@ -324,9 +324,9 @@
                 case GROUP_ADD_FAILED:
                 case GROUP_UPDATE_FAILED:
                 case GROUP_REMOVE_FAILED:
+                case GROUP_BUCKET_FAILOVER:
                     post(event);
                     break;
-
                 default:
                     break;
             }
@@ -353,6 +353,11 @@
             checkValidity();
             store.pushGroupMetrics(deviceId, groupEntries);
         }
+
+        @Override
+        public void notifyOfFailovers(Collection<Group> failoverGroups) {
+            store.notifyOfFailovers(failoverGroups);
+        }
     }
 
     private class InternalDeviceListener implements DeviceListener {
@@ -378,4 +383,5 @@
             }
         }
     }
+
 }
diff --git a/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java b/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java
index afdec73..1f9387e 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java
@@ -1312,6 +1312,17 @@
         }
     }
 
+    @Override
+    public void notifyOfFailovers(Collection<Group> failoverGroups) {
+        List<GroupEvent> failoverEvents = new ArrayList<>();
+        failoverGroups.forEach(group -> {
+            if (group.type() == Group.Type.FAILOVER) {
+                failoverEvents.add(new GroupEvent(GroupEvent.Type.GROUP_BUCKET_FAILOVER, group));
+            }
+        });
+        notifyDelegate(failoverEvents);
+    }
+
     private void garbageCollect(DeviceId deviceId,
                                 Set<Group> southboundGroupEntries,
                                 Set<StoredGroupEntry> storedGroupEntries) {
diff --git a/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProvider.java b/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProvider.java
index 83100f4..159b7dd 100644
--- a/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProvider.java
+++ b/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProvider.java
@@ -18,7 +18,10 @@
 
 import static org.slf4j.LoggerFactory.getLogger;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicLong;
@@ -31,9 +34,12 @@
 import org.onosproject.core.DefaultGroupId;
 import org.onosproject.core.GroupId;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.driver.DriverService;
 import org.onosproject.net.group.DefaultGroup;
 import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
 import org.onosproject.net.group.GroupBuckets;
 import org.onosproject.net.group.GroupDescription;
 import org.onosproject.net.group.GroupOperation;
@@ -42,6 +48,7 @@
 import org.onosproject.net.group.GroupProvider;
 import org.onosproject.net.group.GroupProviderRegistry;
 import org.onosproject.net.group.GroupProviderService;
+import org.onosproject.net.group.GroupService;
 import org.onosproject.net.group.StoredGroupBucketEntry;
 import org.onosproject.net.provider.AbstractProvider;
 import org.onosproject.net.provider.ProviderId;
@@ -62,6 +69,7 @@
 import org.projectfloodlight.openflow.protocol.OFGroupStatsReply;
 import org.projectfloodlight.openflow.protocol.OFGroupType;
 import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFPortDesc;
 import org.projectfloodlight.openflow.protocol.OFPortStatus;
 import org.projectfloodlight.openflow.protocol.OFStatsReply;
 import org.projectfloodlight.openflow.protocol.OFStatsType;
@@ -88,6 +96,12 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DriverService driverService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected GroupService groupService;
+
     private GroupProviderService providerService;
 
     static final int POLL_INTERVAL = 10;
@@ -389,6 +403,7 @@
 
         @Override
         public void portChanged(Dpid dpid, OFPortStatus status) {
+            providerService.notifyOfFailovers(checkFailoverGroups(dpid, status));
         }
 
         @Override
@@ -396,4 +411,58 @@
         }
     }
 
+    /**
+     * Builds a list of failover Groups whose primary live bucket failed over
+     * (i.e. bucket in use has changed).
+     *
+     * @param dpid    DPID of switch whose port's status changed
+     * @param status  new status of port
+     * @return        list of groups whose primary live bucket failed over
+     */
+    private List<Group> checkFailoverGroups(Dpid dpid, OFPortStatus status) {
+        List<Group> groupList = new ArrayList<>();
+        OFPortDesc desc = status.getDesc();
+        PortNumber portNumber = PortNumber.portNumber(desc.getPortNo().getPortNumber());
+        DeviceId id = DeviceId.deviceId(Dpid.uri(dpid));
+        if (desc.isEnabled()) {
+            return groupList;
+        }
+        Iterator<Group> iterator = groupService.getGroups(id).iterator();
+        while (iterator.hasNext()) {
+            Group group = iterator.next();
+            if (group.type() == GroupDescription.Type.FAILOVER &&
+                    checkFailoverGroup(group, id, portNumber)) {
+                groupList.add(group);
+            }
+        }
+    return groupList;
+    }
+
+    /**
+     * Checks whether the first live port in the failover group's bucket
+     * has failed over.
+     *
+     * @param group       failover group to be checked for failover
+     * @param id          device ID of switch whose port's status changed
+     * @param portNumber  port number of port that was disabled
+     * @return            whether the failover group experienced failover
+     */
+    private boolean checkFailoverGroup(Group group, DeviceId id,
+                                       PortNumber portNumber) {
+        boolean portReached = false;
+        boolean portEnabled = false;
+        Iterator<GroupBucket> bIterator = group.buckets().buckets().iterator();
+        GroupBucket bucket;
+        while (bIterator.hasNext() && !portReached) {
+            bucket = bIterator.next();
+            if (deviceService.getPort(id, bucket.watchPort()).isEnabled()) {
+                portEnabled = true;
+            }
+            if (bucket.watchPort().equals(portNumber)) {
+                portReached = true;
+            }
+        }
+        return portReached && !portEnabled;
+    }
+
 }
diff --git a/providers/openflow/group/src/test/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProviderTest.java b/providers/openflow/group/src/test/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProviderTest.java
index ff08f72..cc2cd39 100644
--- a/providers/openflow/group/src/test/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProviderTest.java
+++ b/providers/openflow/group/src/test/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProviderTest.java
@@ -201,6 +201,10 @@
             this.groups = groupEntries;
         }
 
+        @Override
+        public void notifyOfFailovers(Collection<Group> groups) {
+        }
+
         public Collection<Group> getGroupEntries() {
             return groups;
         }