ONOS-885: Group store interface definition and in-memory store implementation

Change-Id: I1125fbc23f1e58bcb8aaf5f67c02da610fa7ef25

ONOS-885: Group store interface definition and in-memory store implementation

Change-Id: Id3794bed63785e10ed86c0b5d90bf875d127224c
diff --git a/core/store/dist/src/main/java/org/onosproject/store/group/impl/package-info.java b/core/store/dist/src/main/java/org/onosproject/store/group/impl/package-info.java
new file mode 100644
index 0000000..603712f
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onosproject/store/group/impl/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2014 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.
+ */
+/**
+ * Implementation of the group store.
+ */
+package org.onosproject.store.group.impl;
\ No newline at end of file
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
new file mode 100644
index 0000000..c82ebc2
--- /dev/null
+++ b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2015 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.store.trivial.impl;
+
+import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.NewConcurrentHashMap;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.Group.GroupState;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupEvent.Type;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupStore;
+import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupEntry;
+import org.onosproject.store.AbstractStore;
+import org.slf4j.Logger;
+
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+
+/**
+ * Manages inventory of group entries using trivial in-memory implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleGroupStore
+        extends AbstractStore<GroupEvent, GroupStoreDelegate>
+        implements GroupStore {
+
+    private final Logger log = getLogger(getClass());
+
+    // inner Map is per device group table
+    private final ConcurrentMap<DeviceId, ConcurrentMap<GroupKey, StoredGroupEntry>>
+            groupEntriesByKey = new ConcurrentHashMap<>();
+    private final ConcurrentMap<DeviceId, ConcurrentMap<GroupId, StoredGroupEntry>>
+            groupEntriesById = new ConcurrentHashMap<>();
+
+    private final AtomicInteger groupIdGen = new AtomicInteger();
+
+    @Activate
+    public void activate() {
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        groupEntriesByKey.clear();
+        groupEntriesById.clear();
+        log.info("Stopped");
+    }
+
+    private static NewConcurrentHashMap<GroupKey, StoredGroupEntry> lazyEmptyGroupKeyTable() {
+        return NewConcurrentHashMap.<GroupKey, StoredGroupEntry>ifNeeded();
+    }
+
+    private static NewConcurrentHashMap<GroupId, StoredGroupEntry> lazyEmptyGroupIdTable() {
+        return NewConcurrentHashMap.<GroupId, StoredGroupEntry>ifNeeded();
+    }
+
+    /**
+     * Returns the group key table for specified device.
+     *
+     * @param deviceId identifier of the device
+     * @return Map representing group key table of given device.
+     */
+    private ConcurrentMap<GroupKey, StoredGroupEntry> getGroupKeyTable(DeviceId deviceId) {
+        return createIfAbsentUnchecked(groupEntriesByKey,
+                                       deviceId, lazyEmptyGroupKeyTable());
+    }
+
+    /**
+     * Returns the group id table for specified device.
+     *
+     * @param deviceId identifier of the device
+     * @return Map representing group key table of given device.
+     */
+    private ConcurrentMap<GroupId, StoredGroupEntry> getGroupIdTable(DeviceId deviceId) {
+        return createIfAbsentUnchecked(groupEntriesById,
+                                       deviceId, lazyEmptyGroupIdTable());
+    }
+
+    /**
+     * Returns the number of groups for the specified device in the store.
+     *
+     * @return number of groups for the specified device
+     */
+    @Override
+    public int getGroupCount(DeviceId deviceId) {
+        return (groupEntriesByKey.get(deviceId) != null) ?
+                 groupEntriesByKey.get(deviceId).size() : 0;
+    }
+
+    /**
+     * Returns the groups associated with a device.
+     *
+     * @param deviceId the device ID
+     *
+     * @return the group entries
+     */
+    @Override
+    public Iterable<Group> getGroups(DeviceId deviceId) {
+        // flatten and make iterator unmodifiable
+        if (groupEntriesByKey.get(deviceId) != null) {
+            return FluentIterable.from(groupEntriesByKey.get(deviceId).values())
+                .transform(
+                        new Function<StoredGroupEntry, Group>() {
+
+                            @Override
+                            public Group apply(
+                                    StoredGroupEntry input) {
+                                return input;
+                            }
+                        });
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the stored group entry.
+     *
+     * @param deviceId the device ID
+     * @param appCookie the group key
+     *
+     * @return a group associated with the key
+     */
+    @Override
+    public Group getGroup(DeviceId deviceId, GroupKey appCookie) {
+        return (groupEntriesByKey.get(deviceId) != null) ?
+                      groupEntriesByKey.get(deviceId).get(appCookie) :
+                      null;
+    }
+
+    /**
+     * Stores a new group entry using the information from group description.
+     *
+     * @param groupDesc group description to be used to create group entry
+     */
+    @Override
+    public void storeGroupDescription(GroupDescription groupDesc) {
+        /* Check if a group is existing with the same key */
+        if (getGroup(groupDesc.deviceId(), groupDesc.appCookie()) != null) {
+            return;
+        }
+
+        /* Get a new group identifier */
+        GroupId id = new DefaultGroupId(groupIdGen.incrementAndGet());
+        /* Create a group entry object */
+        StoredGroupEntry group = new DefaultGroup(id, groupDesc);
+        /* Insert the newly created group entry into concurrent key and id maps */
+        ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+                getGroupKeyTable(groupDesc.deviceId());
+        keyTable.put(groupDesc.appCookie(), group);
+        ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+                getGroupIdTable(groupDesc.deviceId());
+        idTable.put(id, group);
+        notifyDelegate(new GroupEvent(GroupEvent.Type.GROUP_ADD_REQUESTED,
+                                      group));
+    }
+
+    /**
+     * Updates the existing group entry with the information
+     * from group description.
+     *
+     * @param deviceId the device ID
+     * @param oldAppCookie the current group key
+     * @param type update type
+     * @param newGroupDesc group description with updates
+     */
+    @Override
+    public void updateGroupDescription(DeviceId deviceId,
+                                GroupKey oldAppCookie,
+                                UpdateType type,
+                                GroupDescription newGroupDesc) {
+        /* Check if a group is existing with the provided key */
+        Group oldGroup = getGroup(deviceId, oldAppCookie);
+        if (oldGroup == null) {
+            return;
+        }
+
+        List<GroupBucket> newBucketList = getUpdatedBucketList(oldGroup,
+                                                               type,
+                                               newGroupDesc.buckets());
+        if (newBucketList != null) {
+            /* Create a new group object from the old group */
+            GroupBuckets updatedBuckets = new GroupBuckets(newBucketList);
+            GroupDescription updatedGroupDesc = new DefaultGroupDescription(
+                                                        oldGroup.deviceId(),
+                                                        oldGroup.type(),
+                                                        updatedBuckets,
+                                                        newGroupDesc.appCookie(),
+                                                        oldGroup.appId());
+            StoredGroupEntry newGroup = new DefaultGroup(oldGroup.id(),
+                                                     updatedGroupDesc);
+            newGroup.setState(GroupState.PENDING_UPDATE);
+            newGroup.setLife(oldGroup.life());
+            newGroup.setPackets(oldGroup.packets());
+            newGroup.setBytes(oldGroup.bytes());
+            /* Remove the old entry from maps and add new entry
+             * using new key
+             */
+            ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+                    getGroupKeyTable(oldGroup.deviceId());
+            ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+                    getGroupIdTable(oldGroup.deviceId());
+            keyTable.remove(oldGroup.appCookie());
+            idTable.remove(oldGroup.id());
+            keyTable.put(newGroup.appCookie(), newGroup);
+            idTable.put(newGroup.id(), newGroup);
+            notifyDelegate(new GroupEvent(Type.GROUP_UPDATE_REQUESTED, newGroup));
+        }
+    }
+
+    private List<GroupBucket> getUpdatedBucketList(Group oldGroup,
+                                   UpdateType type,
+                                   GroupBuckets buckets) {
+        GroupBuckets oldBuckets = oldGroup.buckets();
+        List<GroupBucket> newBucketList = new ArrayList<GroupBucket>(
+                oldBuckets.buckets());
+        boolean groupDescUpdated = false;
+
+        if (type == UpdateType.ADD) {
+            /* Check if the any of the new buckets are part of the
+             * old bucket list
+             */
+            for (GroupBucket addBucket:buckets.buckets()) {
+                if (!newBucketList.contains(addBucket)) {
+                    newBucketList.add(addBucket);
+                    groupDescUpdated = true;
+                }
+            }
+        } else if (type == UpdateType.REMOVE) {
+            /* Check if the to be removed buckets are part of the
+             * old bucket list
+             */
+            for (GroupBucket removeBucket:buckets.buckets()) {
+                if (newBucketList.contains(removeBucket)) {
+                    newBucketList.remove(removeBucket);
+                    groupDescUpdated = true;
+                }
+            }
+        }
+
+        if (groupDescUpdated) {
+            return newBucketList;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Triggers deleting the existing group entry.
+     *
+     * @param deviceId the device ID
+     * @param appCookie the group key
+     */
+    @Override
+    public void deleteGroupDescription(DeviceId deviceId,
+                                GroupKey appCookie) {
+        /* Check if a group is existing with the provided key */
+        StoredGroupEntry existing = (groupEntriesByKey.get(deviceId) != null) ?
+                           groupEntriesByKey.get(deviceId).get(appCookie) :
+                           null;
+        if (existing == null) {
+            return;
+        }
+
+        synchronized (existing) {
+            existing.setState(GroupState.PENDING_DELETE);
+        }
+        notifyDelegate(new GroupEvent(Type.GROUP_REMOVE_REQUESTED, existing));
+    }
+
+    /**
+     * Stores a new group entry, or updates an existing entry.
+     *
+     * @param group group entry
+     */
+    @Override
+    public void addOrUpdateGroupEntry(Group group) {
+        // check if this new entry is an update to an existing entry
+        StoredGroupEntry existing = (groupEntriesById.get(
+                      group.deviceId()) != null) ?
+                      groupEntriesById.get(group.deviceId()).get(group.id()) :
+                      null;
+        GroupEvent event = null;
+
+        if (existing != null) {
+            synchronized (existing) {
+                existing.setLife(group.life());
+                existing.setPackets(group.packets());
+                existing.setBytes(group.bytes());
+                if (existing.state() == GroupState.PENDING_ADD) {
+                    existing.setState(GroupState.ADDED);
+                    event = new GroupEvent(Type.GROUP_ADDED, existing);
+                } else {
+                    if (existing.state() == GroupState.PENDING_UPDATE) {
+                        existing.setState(GroupState.PENDING_UPDATE);
+                    }
+                    event = new GroupEvent(Type.GROUP_UPDATED, existing);
+                }
+            }
+        }
+
+        if (event != null) {
+            notifyDelegate(event);
+        }
+    }
+
+    /**
+     * Removes the group entry from store.
+     *
+     * @param group group entry
+     */
+    @Override
+    public void removeGroupEntry(Group group) {
+        StoredGroupEntry existing = (groupEntriesById.get(
+                         group.deviceId()) != null) ?
+                         groupEntriesById.get(group.deviceId()).get(group.id()) :
+                         null;
+
+        if (existing != null) {
+            ConcurrentMap<GroupKey, StoredGroupEntry> keyTable =
+                    getGroupKeyTable(existing.deviceId());
+            ConcurrentMap<GroupId, StoredGroupEntry> idTable =
+                    getGroupIdTable(existing.deviceId());
+            idTable.remove(existing.id());
+            keyTable.remove(existing.appCookie());
+            notifyDelegate(new GroupEvent(Type.GROUP_REMOVED, 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
new file mode 100644
index 0000000..b1f03f4
--- /dev/null
+++ b/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright 2014 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.store.trivial.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+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.GroupEvent;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.GroupStore.UpdateType;
+
+/**
+ * Test of the simple DeviceStore implementation.
+ */
+public class SimpleGroupStoreTest {
+
+    private SimpleGroupStore simpleGroupStore;
+
+    public static final DeviceId D1 = deviceId("of:1");
+
+    @Before
+    public void setUp() throws Exception {
+        simpleGroupStore = new SimpleGroupStore();
+        simpleGroupStore.activate();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        simpleGroupStore.deactivate();
+    }
+
+    public class TestGroupKey implements GroupKey {
+        private String groupId;
+
+        public TestGroupKey(String id) {
+            this.groupId = id;
+        }
+
+        public String id() {
+            return this.groupId;
+        }
+
+        @Override
+        public int hashCode() {
+            return groupId.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof TestGroupKey) {
+                return this.groupId.equals(((TestGroupKey) obj).id());
+            }
+            return false;
+        }
+    }
+
+    private class InternalGroupStoreDelegate
+                implements GroupStoreDelegate {
+        private GroupId createdGroupId = null;
+        private GroupKey createdGroupKey;
+        private GroupBuckets createdBuckets;
+        private GroupEvent.Type expectedEvent;
+
+        public InternalGroupStoreDelegate(GroupKey key,
+                                          GroupBuckets buckets,
+                                          GroupEvent.Type expectedEvent) {
+            this.createdBuckets = buckets;
+            this.createdGroupKey = key;
+            this.expectedEvent = expectedEvent;
+        }
+        @Override
+        public void notify(GroupEvent event) {
+            assertEquals(expectedEvent, event.type());
+            assertEquals(Group.Type.SELECT, event.subject().type());
+            assertEquals(D1, event.subject().deviceId());
+            assertEquals(createdGroupKey, event.subject().appCookie());
+            assertEquals(createdBuckets.buckets(), event.subject().buckets().buckets());
+            if (expectedEvent == GroupEvent.Type.GROUP_ADD_REQUESTED) {
+                createdGroupId = event.subject().id();
+                assertEquals(Group.GroupState.PENDING_ADD,
+                             event.subject().state());
+            } else if (expectedEvent == GroupEvent.Type.GROUP_ADDED) {
+                createdGroupId = event.subject().id();
+                assertEquals(Group.GroupState.ADDED,
+                             event.subject().state());
+            } else if (expectedEvent == GroupEvent.Type.GROUP_UPDATE_REQUESTED) {
+                assertEquals(Group.GroupState.PENDING_UPDATE,
+                             event.subject().state());
+            } else if (expectedEvent == GroupEvent.Type.GROUP_REMOVE_REQUESTED) {
+                assertEquals(Group.GroupState.PENDING_DELETE,
+                             event.subject().state());
+            } else if (expectedEvent == GroupEvent.Type.GROUP_REMOVED) {
+                createdGroupId = event.subject().id();
+                assertEquals(Group.GroupState.PENDING_DELETE,
+                             event.subject().state());
+            }
+        }
+
+        public void verifyGroupId(GroupId id) {
+            assertEquals(createdGroupId, id);
+        }
+    }
+
+    @Test
+    public void testGroupStoreOperations() {
+        ApplicationId appId =
+                new DefaultApplicationId(2, "org.groupstore.test");
+        TestGroupKey key = new TestGroupKey("group1");
+        PortNumber[] ports = {PortNumber.portNumber(31),
+                              PortNumber.portNumber(32)};
+        List<PortNumber> outPorts = new ArrayList<PortNumber>();
+        outPorts.add(ports[0]);
+        outPorts.add(ports[1]);
+
+        List<GroupBucket> buckets = new ArrayList<GroupBucket>();
+        for (PortNumber portNumber: outPorts) {
+            TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+            tBuilder.setOutput(portNumber)
+                    .setEthDst(MacAddress.valueOf("00:00:00:00:00:02"))
+                    .setEthSrc(MacAddress.valueOf("00:00:00:00:00:01"))
+                    .pushMpls()
+                    .setMpls(106);
+            buckets.add(DefaultGroupBucket.createSelectGroupBucket(
+                                                        tBuilder.build()));
+        }
+        GroupBuckets groupBuckets = new GroupBuckets(buckets);
+        GroupDescription groupDesc = new DefaultGroupDescription(
+                                                 D1,
+                                                 Group.Type.SELECT,
+                                                 groupBuckets,
+                                                 key,
+                                                 appId);
+        InternalGroupStoreDelegate checkStoreGroupDelegate =
+                new InternalGroupStoreDelegate(key,
+                                               groupBuckets,
+                                               GroupEvent.Type.GROUP_ADD_REQUESTED);
+        simpleGroupStore.setDelegate(checkStoreGroupDelegate);
+        /* Testing storeGroup operation */
+        simpleGroupStore.storeGroupDescription(groupDesc);
+
+        /* Testing getGroupCount operation */
+        assertEquals(1, simpleGroupStore.getGroupCount(D1));
+
+        /* Testing getGroup operation */
+        Group createdGroup = simpleGroupStore.getGroup(D1, key);
+        checkStoreGroupDelegate.verifyGroupId(createdGroup.id());
+
+        /* Testing getGroups operation */
+        Iterable<Group> createdGroups = simpleGroupStore.getGroups(D1);
+        int groupCount = 0;
+        for (Group group:createdGroups) {
+            checkStoreGroupDelegate.verifyGroupId(group.id());
+            groupCount++;
+        }
+        assertEquals(1, groupCount);
+        simpleGroupStore.unsetDelegate(checkStoreGroupDelegate);
+
+        /* Testing addOrUpdateGroupEntry operation from southbound */
+        InternalGroupStoreDelegate addGroupEntryDelegate =
+                new InternalGroupStoreDelegate(key,
+                                               groupBuckets,
+                                               GroupEvent.Type.GROUP_ADDED);
+        simpleGroupStore.setDelegate(addGroupEntryDelegate);
+        simpleGroupStore.addOrUpdateGroupEntry(createdGroup);
+        simpleGroupStore.unsetDelegate(addGroupEntryDelegate);
+
+        /* Testing updateGroupDescription for ADD operation from northbound */
+        TestGroupKey addKey = new TestGroupKey("group1AddBuckets");
+        PortNumber[] newNeighborPorts = {PortNumber.portNumber(41),
+                                         PortNumber.portNumber(42)};
+        List<PortNumber> newOutPorts = new ArrayList<PortNumber>();
+        newOutPorts.add(newNeighborPorts[0]);
+        newOutPorts.add(newNeighborPorts[1]);
+
+        List<GroupBucket> toAddBuckets = new ArrayList<GroupBucket>();
+        for (PortNumber portNumber: newOutPorts) {
+            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(106);
+            toAddBuckets.add(DefaultGroupBucket.createSelectGroupBucket(
+                                                        tBuilder.build()));
+        }
+        GroupBuckets toAddGroupBuckets = new GroupBuckets(toAddBuckets);
+        buckets.addAll(toAddBuckets);
+        GroupBuckets updatedGroupBuckets = new GroupBuckets(buckets);
+        InternalGroupStoreDelegate updateGroupDescDelegate =
+                new InternalGroupStoreDelegate(addKey,
+                                               updatedGroupBuckets,
+                                               GroupEvent.Type.GROUP_UPDATE_REQUESTED);
+        simpleGroupStore.setDelegate(updateGroupDescDelegate);
+        GroupDescription newGroupDesc = new DefaultGroupDescription(
+                                                 D1,
+                                                 Group.Type.SELECT,
+                                                 toAddGroupBuckets,
+                                                 addKey,
+                                                 appId);
+        simpleGroupStore.updateGroupDescription(D1,
+                                                key,
+                                                UpdateType.ADD,
+                                                newGroupDesc);
+        simpleGroupStore.unsetDelegate(updateGroupDescDelegate);
+
+        /* Testing updateGroupDescription for REMOVE operation from northbound */
+        TestGroupKey removeKey = new TestGroupKey("group1RemoveBuckets");
+        List<GroupBucket> toRemoveBuckets = new ArrayList<GroupBucket>();
+        toRemoveBuckets.add(updatedGroupBuckets.buckets().get(0));
+        toRemoveBuckets.add(updatedGroupBuckets.buckets().get(1));
+        GroupBuckets toRemoveGroupBuckets = new GroupBuckets(toRemoveBuckets);
+        List<GroupBucket> remainingBuckets = new ArrayList<GroupBucket>();
+        remainingBuckets.add(updatedGroupBuckets.buckets().get(2));
+        remainingBuckets.add(updatedGroupBuckets.buckets().get(3));
+        GroupBuckets remainingGroupBuckets = new GroupBuckets(remainingBuckets);
+        InternalGroupStoreDelegate removeGroupDescDelegate =
+                new InternalGroupStoreDelegate(removeKey,
+                                               remainingGroupBuckets,
+                                               GroupEvent.Type.GROUP_UPDATE_REQUESTED);
+        simpleGroupStore.setDelegate(removeGroupDescDelegate);
+        GroupDescription removeGroupDesc = new DefaultGroupDescription(
+                                                        D1,
+                                                        Group.Type.SELECT,
+                                                        toRemoveGroupBuckets,
+                                                        removeKey,
+                                                        appId);
+        simpleGroupStore.updateGroupDescription(D1,
+                                                addKey,
+                                                UpdateType.REMOVE,
+                                                removeGroupDesc);
+        simpleGroupStore.unsetDelegate(removeGroupDescDelegate);
+
+        /* Testing getGroup operation */
+        Group existingGroup = simpleGroupStore.getGroup(D1, removeKey);
+        checkStoreGroupDelegate.verifyGroupId(existingGroup.id());
+
+        /* Testing addOrUpdateGroupEntry operation from southbound */
+        InternalGroupStoreDelegate updateGroupEntryDelegate =
+                new InternalGroupStoreDelegate(removeKey,
+                                               remainingGroupBuckets,
+                                               GroupEvent.Type.GROUP_UPDATED);
+        simpleGroupStore.setDelegate(updateGroupEntryDelegate);
+        simpleGroupStore.addOrUpdateGroupEntry(existingGroup);
+        simpleGroupStore.unsetDelegate(updateGroupEntryDelegate);
+
+        /* Testing deleteGroupDescription operation from northbound */
+        InternalGroupStoreDelegate deleteGroupDescDelegate =
+                new InternalGroupStoreDelegate(removeKey,
+                                               remainingGroupBuckets,
+                                               GroupEvent.Type.GROUP_REMOVE_REQUESTED);
+        simpleGroupStore.setDelegate(deleteGroupDescDelegate);
+        simpleGroupStore.deleteGroupDescription(D1, removeKey);
+        simpleGroupStore.unsetDelegate(deleteGroupDescDelegate);
+
+        /* Testing removeGroupEntry operation from southbound */
+        InternalGroupStoreDelegate removeGroupEntryDelegate =
+                new InternalGroupStoreDelegate(removeKey,
+                                               remainingGroupBuckets,
+                                               GroupEvent.Type.GROUP_REMOVED);
+        simpleGroupStore.setDelegate(removeGroupEntryDelegate);
+        simpleGroupStore.removeGroupEntry(existingGroup);
+
+        /* Testing getGroup operation */
+        existingGroup = simpleGroupStore.getGroup(D1, removeKey);
+        assertEquals(null, existingGroup);
+        Iterable<Group> existingGroups  = simpleGroupStore.getGroups(D1);
+        groupCount = 0;
+        for (Group tmp:existingGroups) {
+            /* To avoid warning */
+            assertEquals(null, tmp);
+            groupCount++;
+        }
+        assertEquals(0, groupCount);
+        assertEquals(0, simpleGroupStore.getGroupCount(D1));
+
+        simpleGroupStore.unsetDelegate(removeGroupEntryDelegate);
+
+    }
+}
+