ONOS-1443: Group bucket statistics support and group CLI formatting

Change-Id: Iaa6d8ae1f9222eb9c29d14bf1615a7449e50c4d3
diff --git a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java
index e4910d5..6efd3e7 100644
--- a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java
+++ b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java
@@ -38,12 +38,14 @@
  * in the group. A failover group bucket is associated with a
  * specific port or group that controls its liveness.
  */
-public final class DefaultGroupBucket implements GroupBucket {
+public final class DefaultGroupBucket implements GroupBucket, StoredGroupBucketEntry {
     private final GroupDescription.Type type;
     private final TrafficTreatment treatment;
     private final short weight;
     private final PortNumber watchPort;
     private final GroupId watchGroup;
+    private long packets;
+    private long bytes;
 
     /**
      * Group bucket constructor with the parameters.
@@ -223,6 +225,28 @@
         return toStringHelper(this)
                 .add("type", type)
                 .add("treatment", treatment)
+                .add("packets", packets)
+                .add("bytes", bytes)
                 .toString();
     }
+
+    @Override
+    public long packets() {
+        return packets;
+    }
+
+    @Override
+    public long bytes() {
+        return bytes;
+    }
+
+    @Override
+    public void setPackets(long packets) {
+        this.packets = packets;
+    }
+
+    @Override
+    public void setBytes(long bytes) {
+        this.bytes = bytes;
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java b/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java
index 2bf8882..401c769 100644
--- a/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java
+++ b/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java
@@ -64,4 +64,17 @@
      */
     public GroupId watchGroup();
 
+    /**
+     * Returns the number of packets processed by this group bucket.
+     *
+     * @return number of packets
+     */
+    long packets();
+
+    /**
+     * Returns the number of bytes processed by this group bucket.
+     *
+     * @return number of bytes
+     */
+    long bytes();
 }
diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java b/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java
index 26cd5f6..c0b5e5c 100644
--- a/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java
+++ b/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java
@@ -70,7 +70,7 @@
     @Override
     public String toString() {
         return toStringHelper(this)
-                .add("buckets", buckets)
+                .add("buckets", buckets.toString())
                 .toString();
     }
 }
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupBucketEntry.java b/core/api/src/main/java/org/onosproject/net/group/StoredGroupBucketEntry.java
similarity index 72%
rename from core/api/src/main/java/org/onosproject/net/group/GroupBucketEntry.java
rename to core/api/src/main/java/org/onosproject/net/group/StoredGroupBucketEntry.java
index f7a6617..131875b 100644
--- a/core/api/src/main/java/org/onosproject/net/group/GroupBucketEntry.java
+++ b/core/api/src/main/java/org/onosproject/net/group/StoredGroupBucketEntry.java
@@ -20,18 +20,18 @@
  * group object. A group bucket entry provides additional info of
  * group bucket like statistics...etc
  */
-public interface GroupBucketEntry extends GroupBucket {
+public interface StoredGroupBucketEntry extends GroupBucket {
     /**
-     * Returns Number of packets processed by bucket.
+     * Sets number of packets processed by this group bucket entry.
      *
-     * @return long
+     * @param packets a long value
      */
-    public long packets();
+    void setPackets(long packets);
 
     /**
-     * Returns Number of bytes processed by bucket.
+     * Sets number of bytes processed by this group bucket entry.
      *
-     * @return long
+     * @param bytes a long value
      */
-    public long bytes();
+    void setBytes(long bytes);
 }
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 1930a47..b447732 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
@@ -15,8 +15,10 @@
  */
 package org.onosproject.store.group.impl;
 
+import com.google.common.base.Optional;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -54,6 +56,7 @@
 import org.onosproject.net.group.GroupOperation;
 import org.onosproject.net.group.GroupStore;
 import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupBucketEntry;
 import org.onosproject.net.group.StoredGroupEntry;
 import org.onosproject.store.AbstractStore;
 import org.onosproject.store.Timestamp;
@@ -624,6 +627,22 @@
                     group.id(),
                     group.deviceId());
             synchronized (existing) {
+                for (GroupBucket bucket:group.buckets().buckets()) {
+                    java.util.Optional<GroupBucket> matchingBucket =
+                            existing.buckets().buckets()
+                            .stream()
+                            .filter((existingBucket)->(existingBucket.equals(bucket)))
+                            .findFirst();
+                    if (matchingBucket.isPresent()) {
+                        ((StoredGroupBucketEntry) matchingBucket.
+                                get()).setPackets(bucket.packets());
+                        ((StoredGroupBucketEntry) matchingBucket.
+                                get()).setBytes(bucket.bytes());
+                    } else {
+                        log.warn("addOrUpdateGroupEntry: No matching "
+                                + "buckets to update stats");
+                    }
+                }
                 existing.setLife(group.life());
                 existing.setPackets(group.packets());
                 existing.setBytes(group.bytes());
diff --git a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java
index d35a5c0..7c4f8c31 100644
--- a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java
+++ b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java
@@ -46,6 +46,7 @@
 import org.onosproject.net.group.GroupOperation;
 import org.onosproject.net.group.GroupStore;
 import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupBucketEntry;
 import org.onosproject.net.group.StoredGroupEntry;
 import org.onosproject.store.AbstractStore;
 import org.slf4j.Logger;
@@ -416,6 +417,22 @@
 
         if (existing != null) {
             synchronized (existing) {
+                for (GroupBucket bucket:group.buckets().buckets()) {
+                    java.util.Optional<GroupBucket> matchingBucket =
+                            existing.buckets().buckets()
+                            .stream()
+                            .filter((existingBucket)->(existingBucket.equals(bucket)))
+                            .findFirst();
+                    if (matchingBucket.isPresent()) {
+                        ((StoredGroupBucketEntry) matchingBucket.
+                                get()).setPackets(bucket.packets());
+                        ((StoredGroupBucketEntry) matchingBucket.
+                                get()).setBytes(bucket.bytes());
+                    } else {
+                        log.warn("addOrUpdateGroupEntry: No matching "
+                                + "buckets to update stats");
+                    }
+                }
                 existing.setLife(group.life());
                 existing.setPackets(group.packets());
                 existing.setBytes(group.bytes());
@@ -424,7 +441,7 @@
                     event = new GroupEvent(Type.GROUP_ADDED, existing);
                 } else {
                     if (existing.state() == GroupState.PENDING_UPDATE) {
-                        existing.setState(GroupState.PENDING_UPDATE);
+                        existing.setState(GroupState.ADDED);
                     }
                     event = new GroupEvent(Type.GROUP_UPDATED, existing);
                 }
diff --git a/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java b/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java
index 92ae5d7..61d60b1 100644
--- a/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java
+++ b/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java
@@ -34,6 +34,7 @@
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.DefaultGroup;
 import org.onosproject.net.group.DefaultGroupBucket;
 import org.onosproject.net.group.DefaultGroupDescription;
 import org.onosproject.net.group.DefaultGroupKey;
@@ -46,6 +47,8 @@
 import org.onosproject.net.group.GroupOperation;
 import org.onosproject.net.group.GroupStore.UpdateType;
 import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupBucketEntry;
+import org.onosproject.net.group.StoredGroupEntry;
 
 import com.google.common.collect.Iterables;
 
@@ -100,6 +103,26 @@
                 createdGroupId = event.subject().id();
                 assertEquals(Group.GroupState.ADDED,
                              event.subject().state());
+            } else if (expectedEvent == GroupEvent.Type.GROUP_UPDATED) {
+                createdGroupId = event.subject().id();
+                assertEquals(true,
+                             event.subject().buckets().
+                             buckets().containsAll(createdBuckets.buckets()));
+                assertEquals(true,
+                             createdBuckets.buckets().
+                             containsAll(event.subject().buckets().buckets()));
+                for (GroupBucket bucket:event.subject().buckets().buckets()) {
+                    java.util.Optional<GroupBucket> matched = createdBuckets.buckets()
+                            .stream()
+                            .filter((expected) -> expected.equals(bucket))
+                            .findFirst();
+                    assertEquals(matched.get().packets(),
+                                 bucket.packets());
+                    assertEquals(matched.get().bytes(),
+                                 bucket.bytes());
+                }
+                assertEquals(Group.GroupState.ADDED,
+                             event.subject().state());
             } else if (expectedEvent == GroupEvent.Type.GROUP_UPDATE_REQUESTED) {
                 assertEquals(Group.GroupState.PENDING_UPDATE,
                              event.subject().state());
@@ -243,13 +266,33 @@
     // Testing addOrUpdateGroupEntry operation from southbound
     private void testUpdateGroupEntryFromSB(GroupKey currKey) {
         Group existingGroup = simpleGroupStore.getGroup(D1, currKey);
+        int totalPkts = 0;
+        int totalBytes = 0;
+        List<GroupBucket> newBucketList = new ArrayList<GroupBucket>();
+        for (GroupBucket bucket:existingGroup.buckets().buckets()) {
+            StoredGroupBucketEntry newBucket =
+                    (StoredGroupBucketEntry)
+                    DefaultGroupBucket.createSelectGroupBucket(bucket.treatment());
+            newBucket.setPackets(10);
+            newBucket.setBytes(10 * 256 * 8);
+            totalPkts += 10;
+            totalBytes += 10 * 256 * 8;
+            newBucketList.add(newBucket);
+        }
+        GroupBuckets updatedBuckets = new GroupBuckets(newBucketList);
+        Group updatedGroup = new DefaultGroup(existingGroup.id(),
+                                              existingGroup.deviceId(),
+                                              existingGroup.type(),
+                                              updatedBuckets);
+        ((StoredGroupEntry) updatedGroup).setPackets(totalPkts);
+        ((StoredGroupEntry) updatedGroup).setBytes(totalBytes);
 
         InternalGroupStoreDelegate updateGroupEntryDelegate =
                 new InternalGroupStoreDelegate(currKey,
-                                               existingGroup.buckets(),
+                                               updatedBuckets,
                                                GroupEvent.Type.GROUP_UPDATED);
         simpleGroupStore.setDelegate(updateGroupEntryDelegate);
-        simpleGroupStore.addOrUpdateGroupEntry(existingGroup);
+        simpleGroupStore.addOrUpdateGroupEntry(updatedGroup);
         simpleGroupStore.unsetDelegate(updateGroupEntryDelegate);
     }