[ONOS-5797] GroupService: add setBucketsForGroup

Add method to set all buckets from a group, overwriting the
previous group buckets entirely. Useful for edits that before
required two operations: removing the buckets and then adding
others. It can all be done with one OF message in the end.

Change-Id: Ic5669603ed4fd18b8efaa8d0253ab9d7b1e870f5
diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupService.java b/core/api/src/main/java/org/onosproject/net/group/GroupService.java
index 47e0882..ef62783 100644
--- a/core/api/src/main/java/org/onosproject/net/group/GroupService.java
+++ b/core/api/src/main/java/org/onosproject/net/group/GroupService.java
@@ -107,6 +107,26 @@
                                 ApplicationId appId);
 
     /**
+     * Set buckets for an existing group. The caller can optionally
+     * associate a new cookie during this updation. GROUP_UPDATED or
+     * GROUP_UPDATE_FAILED notifications would be provided along with
+     * cookie depending on the result of the operation on the device.
+     *
+     * This operation overwrites the previous group buckets entirely.
+     *
+     * @param deviceId  device identifier
+     * @param oldCookie cookie to be used to retrieve the existing group
+     * @param buckets   immutable list of group buckets to be set
+     * @param newCookie immutable cookie to be used post update operation
+     * @param appId     Application Id
+     */
+    default void setBucketsForGroup(DeviceId deviceId,
+                                    GroupKey oldCookie,
+                                    GroupBuckets buckets,
+                                    GroupKey newCookie,
+                                    ApplicationId appId) {}
+
+    /**
      * Purges all the group entries on the specified device.
      * @param deviceId device identifier
      */
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 0c0c06c..4559e64 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
@@ -34,7 +34,12 @@
         /**
          * Modify existing group by removing provided information from it.
          */
-        REMOVE
+        REMOVE,
+        /**
+         * Modify existing group entry by setting the provided information,
+         * overwriting the previous group entry entirely.
+         */
+        SET
     }
 
     /**
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 01930ef..42a6d56 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
@@ -321,6 +321,10 @@
     private List<GroupBucket> getUpdatedBucketList(Group oldGroup,
                                                    UpdateType type,
                                                    GroupBuckets buckets) {
+        if (type == UpdateType.SET) {
+            return buckets.buckets();
+        }
+
         List<GroupBucket> oldBuckets = oldGroup.buckets().buckets();
         List<GroupBucket> updatedBucketList = new ArrayList<>();
         boolean groupDescUpdated = false;
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java
index 1e6f865..2f87a5c 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleGroupStoreTest.java
@@ -204,6 +204,11 @@
         newKey = new DefaultGroupKey("group1RemoveBuckets".getBytes());
         testRemoveBuckets(currKey, newKey);
 
+        // Testing updateGroupDescription for SET operation from northbound
+        currKey = newKey;
+        newKey = new DefaultGroupKey("group1SetBuckets".getBytes());
+        testSetBuckets(currKey, newKey);
+
         // Testing addOrUpdateGroupEntry operation from southbound
         currKey = newKey;
         testUpdateGroupEntryFromSB(currKey);
@@ -418,6 +423,35 @@
         simpleGroupStore.unsetDelegate(removeGroupDescDelegate);
     }
 
+    // Testing updateGroupDescription for SET operation from northbound
+    private void testSetBuckets(GroupKey currKey, GroupKey setKey) {
+        List<GroupBucket> toSetBuckets = new ArrayList<>();
+
+        short weight = 5;
+        PortNumber portNumber = PortNumber.portNumber(42);
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        tBuilder.setOutput(portNumber)
+                .setEthDst(MacAddress.valueOf("00:00:00:00:00:03"))
+                .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+                .pushMpls()
+                .setMpls(MplsLabel.mplsLabel(106));
+        toSetBuckets.add(DefaultGroupBucket.createSelectGroupBucket(
+                tBuilder.build(), weight));
+
+        GroupBuckets toSetGroupBuckets = new GroupBuckets(toSetBuckets);
+        InternalGroupStoreDelegate updateGroupDescDelegate =
+                new InternalGroupStoreDelegate(setKey,
+                        toSetGroupBuckets,
+                        GroupEvent.Type.GROUP_UPDATE_REQUESTED);
+        simpleGroupStore.setDelegate(updateGroupDescDelegate);
+        simpleGroupStore.updateGroupDescription(D1,
+                currKey,
+                UpdateType.SET,
+                toSetGroupBuckets,
+                setKey);
+        simpleGroupStore.unsetDelegate(updateGroupDescDelegate);
+    }
+
     // Testing deleteGroupDescription operation from northbound
     private void testDeleteGroup(GroupKey currKey) {
         Group existingGroup = simpleGroupStore.getGroup(D1, currKey);
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 09930d2..bcca209 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
@@ -224,6 +224,34 @@
                                      newCookie);
     }
 
+    /**
+     * Set buckets for an existing group. The caller can optionally
+     * associate a new cookie during this updation. GROUP_UPDATED or
+     * GROUP_UPDATE_FAILED notifications would be provided along with
+     * cookie depending on the result of the operation on the device.
+     *
+     * This operation overwrites the previous group buckets entirely.
+     *
+     * @param deviceId  device identifier
+     * @param oldCookie cookie to be used to retrieve the existing group
+     * @param buckets   immutable list of group buckets to be set
+     * @param newCookie immutable cookie to be used post update operation
+     * @param appId     Application Id
+     */
+    @Override
+    public void setBucketsForGroup(DeviceId deviceId,
+                                   GroupKey oldCookie,
+                                   GroupBuckets buckets,
+                                   GroupKey newCookie,
+                                   ApplicationId appId) {
+        checkPermission(GROUP_WRITE);
+        store.updateGroupDescription(deviceId,
+                oldCookie,
+                UpdateType.SET,
+                buckets,
+                newCookie);
+    }
+
     @Override
     public void purgeGroupEntries(DeviceId deviceId) {
         checkPermission(GROUP_WRITE);
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 28e3302..88a1d54 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
@@ -719,6 +719,10 @@
     private List<GroupBucket> getUpdatedBucketList(Group oldGroup,
                                                    UpdateType type,
                                                    GroupBuckets buckets) {
+        if (type == UpdateType.SET) {
+            return buckets.buckets();
+        }
+
         List<GroupBucket> oldBuckets = oldGroup.buckets().buckets();
         List<GroupBucket> updatedBucketList = new ArrayList<>();
         boolean groupDescUpdated = false;
diff --git a/core/store/dist/src/test/java/org/onosproject/store/group/impl/DistributedGroupStoreTest.java b/core/store/dist/src/test/java/org/onosproject/store/group/impl/DistributedGroupStoreTest.java
index a3ca1ec..e9cebef 100644
--- a/core/store/dist/src/test/java/org/onosproject/store/group/impl/DistributedGroupStoreTest.java
+++ b/core/store/dist/src/test/java/org/onosproject/store/group/impl/DistributedGroupStoreTest.java
@@ -430,6 +430,20 @@
                 assertEquals(weight, bucket.weight());
             }
         }
+
+        buckets = new GroupBuckets(ImmutableList.of(selectGroupBucketWithWeight));
+
+        groupStore.updateGroupDescription(deviceId1,
+                newKey,
+                SET,
+                buckets,
+                newKey);
+
+        group1 = groupStore.getGroup(deviceId1, groupId1);
+        assertThat(group1.appCookie(), is(newKey));
+        assertThat(group1.buckets().buckets(), hasSize(1));
+        GroupBucket onlyBucket = group1.buckets().buckets().iterator().next();
+        assertEquals(weight, onlyBucket.weight());
     }
 
     @Test