@Override
    public void groupOperationFailed(DeviceId deviceId, GroupOperation operation) {

        StoredGroupEntry existing = getStoredGroupEntry(deviceId,
                                                        operation.groupId());

        if (existing == null) {
            log.warn("No group entry with ID {} found ", operation.groupId());
            return;
        }

        log.warn("groupOperationFailed: group operation {} failed in state {} "
                         + "for group {} in device {} with code {}",
                 operation.opType(),
                 existing.state(),
                 existing.id(),
                 existing.deviceId(),
                 operation.failureCode());
        if (operation.failureCode() == GroupOperation.GroupMsgErrorCode.GROUP_EXISTS) {
            if (operation.buckets().equals(existing.buckets())) {
                if (existing.state() == GroupState.PENDING_ADD ||
                        existing.state() == GroupState.PENDING_ADD_RETRY) {
                    log.info("GROUP_EXISTS: GroupID and Buckets match for group in pending "
                                     + "add state - moving to ADDED for group {} in device {}",
                             existing.id(), deviceId);
                    addOrUpdateGroupEntry(existing);
                    return;
                } else {
                    log.warn("GROUP_EXISTS: GroupId and Buckets match but existing"
                            + "group in state: {}", existing.state());
                }
            } else {
                log.warn("GROUP EXISTS: Group ID matched but buckets did not. "
                        + "Operation: {} Existing: {}", operation.buckets(),
                        existing.buckets());
            }
        }
        if (operation.failureCode() == GroupOperation.GroupMsgErrorCode.INVALID_GROUP) {
            existing.incrFailedRetryCount();
            if (existing.failedRetryCount() < MAX_FAILED_ATTEMPTS) {
                log.warn("Group {} programming failed {} of {} times in dev {}, "
                        + "retrying ..", existing.id(),
                         existing.failedRetryCount(), MAX_FAILED_ATTEMPTS,
                         deviceId);
                return;
            }
            log.warn("Group {} programming failed {} of {} times in dev {}, "
                    + "removing group from store", existing.id(),
                     existing.failedRetryCount(), MAX_FAILED_ATTEMPTS,
                     deviceId);
            // fall through to case
        }

        switch (operation.opType()) {
            case ADD:
                if (existing.state() == GroupState.PENDING_ADD
                    || existing.state() == GroupState.PENDING_ADD_RETRY) {
                    notifyDelegate(new GroupEvent(Type.GROUP_ADD_FAILED, existing));
                    log.warn("groupOperationFailed: cleaning up "
                                     + "group {} from store in device {}....",
                             existing.id(),
                             existing.deviceId());
                    //Removal from groupid based map will happen in the
                    //map update listener
                    getGroupStoreKeyMap().remove(new GroupStoreKeyMapKey(existing.deviceId(),
                                                                         existing.appCookie()));
                }
                break;
            case MODIFY:
                notifyDelegate(new GroupEvent(Type.GROUP_UPDATE_FAILED, existing));
                break;
            case DELETE:
                notifyDelegate(new GroupEvent(Type.GROUP_REMOVE_FAILED, existing));
                break;
            default:
                log.warn("Unknown group operation type {}", operation.opType());
        }
    }
@Test
    public void testGroupOperationFailed() {
        TestDelegate delegate = new TestDelegate();
        groupStore.setDelegate(delegate);
        groupStore.deviceInitialAuditCompleted(deviceId1, true);
        groupStore.deviceInitialAuditCompleted(deviceId2, true);

        groupStore.storeGroupDescription(groupDescription1);
        groupStore.storeGroupDescription(groupDescription2);

        List<GroupEvent> eventsAfterAdds = delegate.eventsSeen();
        assertThat(eventsAfterAdds, hasSize(2));
        eventsAfterAdds.forEach(event -> assertThat(event.type(), is(GroupEvent.Type.GROUP_ADD_REQUESTED)));
        delegate.resetEvents();

        GroupOperation opAdd =
                GroupOperation.createAddGroupOperation(groupId1,
                        INDIRECT,
                        indirectGroupBuckets);
        groupStore.groupOperationFailed(deviceId1, opAdd);

        List<GroupEvent> eventsAfterAddFailed = delegate.eventsSeen();
        assertThat(eventsAfterAddFailed, hasSize(2));
        assertThat(eventsAfterAddFailed.get(0).type(),
                is(GroupEvent.Type.GROUP_ADD_FAILED));
        assertThat(eventsAfterAddFailed.get(1).type(),
                is(GroupEvent.Type.GROUP_REMOVED));
        delegate.resetEvents();

        GroupOperation opModify =
                GroupOperation.createModifyGroupOperation(groupId2,
                        INDIRECT,
                        indirectGroupBuckets);
        groupStore.groupOperationFailed(deviceId2, opModify);
        List<GroupEvent> eventsAfterModifyFailed = delegate.eventsSeen();
        assertThat(eventsAfterModifyFailed, hasSize(1));
        assertThat(eventsAfterModifyFailed.get(0).type(),
                is(GroupEvent.Type.GROUP_UPDATE_FAILED));
        delegate.resetEvents();

        GroupOperation opDelete =
                GroupOperation.createDeleteGroupOperation(groupId2,
                        INDIRECT);
        groupStore.groupOperationFailed(deviceId2, opDelete);
        List<GroupEvent> eventsAfterDeleteFailed = delegate.eventsSeen();
        assertThat(eventsAfterDeleteFailed, hasSize(1));
        assertThat(eventsAfterDeleteFailed.get(0).type(),
                is(GroupEvent.Type.GROUP_REMOVE_FAILED));
        delegate.resetEvents();
    }