Add support for SDL groups 94/194/3 v0.2.0
authorMarco Tallskog <marco.tallskog@nokia.com>
Fri, 24 May 2019 13:17:02 +0000 (16:17 +0300)
committerMarco Tallskog <marco.tallskog@nokia.com>
Mon, 27 May 2019 11:29:33 +0000 (14:29 +0300)
SDL group is an unordered collection of members where each member is
unique.

Add the following methods to do operations with groups:
-AddMember: adds number of members into a given group. It is possible to
give already existing members to be added but that doesn't have an
effect to group.
-RemoveMember: Remove number of members from a group. It is possible to
try to remove non-existing members from a group without an error.
-RemoveGroup: Remove the whole group along with its members.
-GetMembers: Get all members from a group.
-IsMember: Check if a specific member is part of the group.
-GroupSize: Return the number of members in group.

Change-Id: I9005ffe584aa0872c5a3fab170d0ac37d9b9f30a
Signed-off-by: Marco Tallskog <marco.tallskog@nokia.com>
bench_test.go
internal/sdlgoredis/sdlgoredis.go
sdl.go
sdl_test.go

index fa60686..a4b2e13 100644 (file)
@@ -39,6 +39,12 @@ type multiBenchmark struct {
        valueSize int
 }
 
+type setBenchmark struct {
+       key         string
+       member      string
+       memberCount int
+}
+
 func (bm singleBenchmark) String(oper string) string {
        return fmt.Sprintf("op = %s key=%d value=%d", oper, bm.keySize, bm.valueSize)
 }
@@ -46,6 +52,10 @@ func (bm singleBenchmark) String(oper string) string {
 func (bm multiBenchmark) String(oper string) string {
        return fmt.Sprintf("op = %s keycnt=%d key=%d value=%d", oper, bm.keyCount, bm.keySize, bm.valueSize)
 }
+
+func (bm setBenchmark) String(oper string) string {
+       return fmt.Sprintf("op = %s, memberCount=%d", oper, bm.memberCount)
+}
 func BenchmarkSet(b *testing.B) {
        benchmarks := []singleBenchmark{
                {"a", 10, 64},
@@ -188,3 +198,32 @@ func BenchmarkMultiGet(b *testing.B) {
                })
        }
 }
+
+func BenchmarkSetAddMember(b *testing.B) {
+       benchmarks := []setBenchmark{
+               {"a", "x", 1},
+               {"b", "x", 100},
+               {"c", "x", 10000},
+               {"d", "x", 1000000},
+       }
+
+       for _, bm := range benchmarks {
+               b.Run(bm.String("AddMember"), func(b *testing.B) {
+                       sdl := sdlgo.NewSdlInstance("namespace", sdlgo.NewDatabase())
+                       members := make([]string, 0)
+                       for i := 0; i < bm.memberCount; i++ {
+                               member := bm.member + strconv.Itoa(i)
+                               members = append(members, member)
+                       }
+                       b.ResetTimer()
+                       b.RunParallel(func(pb *testing.PB) {
+                               for pb.Next() {
+                                       err := sdl.AddMember(bm.key, members)
+                                       if err != nil {
+                                               b.Fatal(err)
+                                       }
+                               }
+                       })
+               })
+       }
+}
index 1085d2c..b032d8f 100644 (file)
@@ -250,3 +250,28 @@ func (db *DB) DelIE(key string, data interface{}) (bool, error) {
        }
        return checkIntResultAndError(db.client.Do("DELIE", key, data).Result())
 }
+
+func (db *DB) SAdd(key string, data ...interface{}) error {
+       _, err := db.client.SAdd(key, data...).Result()
+       return err
+}
+
+func (db *DB) SRem(key string, data ...interface{}) error {
+       _, err := db.client.SRem(key, data...).Result()
+       return err
+}
+
+func (db *DB) SMembers(key string) ([]string, error) {
+       result, err := db.client.SMembers(key).Result()
+       return result, err
+}
+
+func (db *DB) SIsMember(key string, data interface{}) (bool, error) {
+       result, err := db.client.SIsMember(key, data).Result()
+       return result, err
+}
+
+func (db *DB) SCard(key string) (int64, error) {
+       result, err := db.client.SCard(key).Result()
+       return result, err
+}
diff --git a/sdl.go b/sdl.go
index 9eee8c9..e25a85d 100644 (file)
--- a/sdl.go
+++ b/sdl.go
@@ -26,24 +26,6 @@ import (
        "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/sdlgoredis"
 )
 
-type iDatabase interface {
-       SubscribeChannelDB(cb sdlgoredis.ChannelNotificationCb, channelPrefix, eventSeparator string, channels ...string)
-       UnsubscribeChannelDB(channels ...string)
-       MSet(pairs ...interface{}) error
-       MSetPub(ns, message string, pairs ...interface{}) error
-       MGet(keys []string) ([]interface{}, error)
-       CloseDB() error
-       Del(keys []string) error
-       DelPub(channel, message string, keys []string) error
-       Keys(key string) ([]string, error)
-       SetIE(key string, oldData, newData interface{}) (bool, error)
-       SetIEPub(channel, message, key string, oldData, newData interface{}) (bool, error)
-       SetNX(key string, data interface{}) (bool, error)
-       SetNXPub(channel, message, key string, data interface{}) (bool, error)
-       DelIE(key string, data interface{}) (bool, error)
-       DelIEPub(channel, message, key string, data interface{}) (bool, error)
-}
-
 //SdlInstance provides an API to read, write and modify
 //key-value pairs in a given namespace.
 type SdlInstance struct {
@@ -428,5 +410,73 @@ func (s *SdlInstance) RemoveAllAndPublish(channelsAndEvents []string) error {
                err = s.DelPub(channelsAndEventsPrepared[0], channelsAndEventsPrepared[1], keys)
        }
        return err
+}
+
+//AddMember adds a new members to a group.
+//
+//SDL groups are unordered collections of members where each member is
+//unique. It is possible to add the same member several times without the
+//need to check if it already exists.
+func (s *SdlInstance) AddMember(group string, member ...interface{}) error {
+       return s.SAdd(s.nsPrefix+group, member...)
+}
+
+//RemoveMember removes members from a group.
+func (s *SdlInstance) RemoveMember(group string, member ...interface{}) error {
+       return s.SRem(s.nsPrefix+group, member...)
+}
+
+//RemoveGroup removes the whole group along with it's members.
+func (s *SdlInstance) RemoveGroup(group string) error {
+       return s.Del([]string{s.nsPrefix + group})
+}
+
+//GetMembers returns all the members from a group.
+func (s *SdlInstance) GetMembers(group string) ([]string, error) {
+       retVal, err := s.SMembers(s.nsPrefix + group)
+       if err != nil {
+               return []string{}, err
+       }
+       return retVal, err
+}
+
+//IsMember returns true if given member is found from a group.
+func (s *SdlInstance) IsMember(group string, member interface{}) (bool, error) {
+       retVal, err := s.SIsMember(s.nsPrefix+group, member)
+       if err != nil {
+               return false, err
+       }
+       return retVal, err
+}
 
+//GroupSize returns the number of members in a group.
+func (s *SdlInstance) GroupSize(group string) (int64, error) {
+       retVal, err := s.SCard(s.nsPrefix + group)
+       if err != nil {
+               return 0, err
+       }
+       return retVal, err
+}
+
+type iDatabase interface {
+       SubscribeChannelDB(cb sdlgoredis.ChannelNotificationCb, channelPrefix, eventSeparator string, channels ...string)
+       UnsubscribeChannelDB(channels ...string)
+       MSet(pairs ...interface{}) error
+       MSetPub(ns, message string, pairs ...interface{}) error
+       MGet(keys []string) ([]interface{}, error)
+       CloseDB() error
+       Del(keys []string) error
+       DelPub(channel, message string, keys []string) error
+       Keys(key string) ([]string, error)
+       SetIE(key string, oldData, newData interface{}) (bool, error)
+       SetIEPub(channel, message, key string, oldData, newData interface{}) (bool, error)
+       SetNX(key string, data interface{}) (bool, error)
+       SetNXPub(channel, message, key string, data interface{}) (bool, error)
+       DelIE(key string, data interface{}) (bool, error)
+       DelIEPub(channel, message, key string, data interface{}) (bool, error)
+       SAdd(key string, data ...interface{}) error
+       SRem(key string, data ...interface{}) error
+       SMembers(key string) ([]string, error)
+       SIsMember(key string, data interface{}) (bool, error)
+       SCard(key string) (int64, error)
 }
index a941f83..3d06bb4 100644 (file)
@@ -104,6 +104,30 @@ func (m *mockDB) DelIEPub(channel, message, key string, data interface{}) (bool,
        return a.Bool(0), a.Error(1)
 }
 
+func (m *mockDB) SAdd(key string, data ...interface{}) error {
+       a := m.Called(key, data)
+       return a.Error(0)
+}
+
+func (m *mockDB) SRem(key string, data ...interface{}) error {
+       a := m.Called(key, data)
+       return a.Error(0)
+}
+func (m *mockDB) SMembers(key string) ([]string, error) {
+       a := m.Called(key)
+       return a.Get(0).([]string), a.Error(1)
+}
+
+func (m *mockDB) SIsMember(key string, data interface{}) (bool, error) {
+       a := m.Called(key, data)
+       return a.Bool(0), a.Error(1)
+}
+
+func (m *mockDB) SCard(key string) (int64, error) {
+       a := m.Called(key)
+       return a.Get(0).(int64), a.Error(1)
+}
+
 func setup() (*mockDB, *sdlgo.SdlInstance) {
        m := new(mockDB)
        i := sdlgo.NewSdlInstance("namespace", m)
@@ -1038,3 +1062,173 @@ func TestRemoveAllAndPublishIncorrectChannel(t *testing.T) {
        m.AssertExpectations(t)
 
 }
+
+func TestAddMemberSuccessfully(t *testing.T) {
+       m, i := setup()
+
+       groupExpected := string("{namespace},group")
+       membersExpected := []interface{}{"member1", "member2"}
+
+       m.On("SAdd", groupExpected, membersExpected).Return(nil)
+
+       err := i.AddMember("group", "member1", "member2")
+       assert.Nil(t, err)
+       m.AssertExpectations(t)
+}
+
+func TestAddMemberFail(t *testing.T) {
+       m, i := setup()
+
+       groupExpected := string("{namespace},group")
+       membersExpected := []interface{}{"member1", "member2"}
+
+       m.On("SAdd", groupExpected, membersExpected).Return(errors.New("Some error"))
+
+       err := i.AddMember("group", "member1", "member2")
+       assert.NotNil(t, err)
+       m.AssertExpectations(t)
+}
+func TestRemoveMemberSuccessfully(t *testing.T) {
+       m, i := setup()
+
+       groupExpected := string("{namespace},group")
+       membersExpected := []interface{}{"member1", "member2"}
+
+       m.On("SRem", groupExpected, membersExpected).Return(nil)
+
+       err := i.RemoveMember("group", "member1", "member2")
+       assert.Nil(t, err)
+       m.AssertExpectations(t)
+}
+
+func TestRemoveMemberFail(t *testing.T) {
+       m, i := setup()
+
+       groupExpected := string("{namespace},group")
+       membersExpected := []interface{}{"member1", "member2"}
+
+       m.On("SRem", groupExpected, membersExpected).Return(errors.New("Some error"))
+
+       err := i.RemoveMember("group", "member1", "member2")
+       assert.NotNil(t, err)
+       m.AssertExpectations(t)
+}
+
+func TestRemoveGroupSuccessfully(t *testing.T) {
+       m, i := setup()
+
+       groupExpected := []string{"{namespace},group"}
+
+       m.On("Del", groupExpected).Return(nil)
+
+       err := i.RemoveGroup("group")
+       assert.Nil(t, err)
+       m.AssertExpectations(t)
+}
+func TestRemoveGroupFail(t *testing.T) {
+       m, i := setup()
+
+       groupExpected := []string{"{namespace},group"}
+
+       m.On("Del", groupExpected).Return(errors.New("Some error"))
+
+       err := i.RemoveGroup("group")
+       assert.NotNil(t, err)
+       m.AssertExpectations(t)
+}
+
+func TestGetMembersSuccessfully(t *testing.T) {
+       m, i := setup()
+
+       groupExpected := "{namespace},group"
+       returnExpected := []string{"member1", "member2"}
+
+       m.On("SMembers", groupExpected).Return(returnExpected, nil)
+
+       result, err := i.GetMembers("group")
+       assert.Nil(t,err)
+       assert.Equal(t, result, returnExpected)
+       m.AssertExpectations(t)
+}
+func TestGetMembersFail(t *testing.T) {
+       m, i := setup()
+
+       groupExpected := "{namespace},group"
+       returnExpected := []string{"member1", "member2"}
+
+       m.On("SMembers", groupExpected).Return(returnExpected, errors.New("Some error"))
+
+       result, err := i.GetMembers("group")
+       assert.NotNil(t,err)
+       assert.Equal(t, []string{}, result)
+       m.AssertExpectations(t)
+}
+
+func TestIsMemberSuccessfullyIsMember(t *testing.T) {
+       m, i := setup()
+
+       groupExpected := "{namespace},group"
+       memberExpected := "member"
+
+       m.On("SIsMember", groupExpected, memberExpected).Return(true, nil)
+
+       result, err := i.IsMember("group", "member")
+       assert.Nil(t, err)
+       assert.True(t, result)
+       m.AssertExpectations(t)
+}
+func TestIsMemberSuccessfullyIsNotMember(t *testing.T) {
+       m, i := setup()
+
+       groupExpected := "{namespace},group"
+       memberExpected := "member"
+
+       m.On("SIsMember", groupExpected, memberExpected).Return(false, nil)
+
+       result, err := i.IsMember("group", "member")
+       assert.Nil(t, err)
+       assert.False(t, result)
+       m.AssertExpectations(t)
+}
+func TestIsMemberFailure(t *testing.T) {
+       m, i := setup()
+
+       groupExpected := "{namespace},group"
+       memberExpected := "member"
+
+       m.On("SIsMember", groupExpected, memberExpected).Return(true, errors.New("Some error"))
+
+       result, err := i.IsMember("group", "member")
+       assert.NotNil(t, err)
+       assert.False(t, result)
+       m.AssertExpectations(t)
+}
+
+func TestGroupSizeSuccessfully(t *testing.T) {
+       m, i := setup()
+
+       var expectedSize int64
+       expectedSize = 2
+       groupExpected := "{namespace},group"
+
+       m.On("SCard", groupExpected).Return(expectedSize, nil)
+
+       result, err := i.GroupSize("group")
+       assert.Nil(t, err)
+       assert.Equal(t, expectedSize, result)
+       m.AssertExpectations(t)
+}
+func TestGroupSizeFail(t *testing.T) {
+       m, i := setup()
+
+       var expectedSize int64
+       expectedSize = 2
+       groupExpected := "{namespace},group"
+
+       m.On("SCard", groupExpected).Return(expectedSize, errors.New("Some error"))
+
+       result, err := i.GroupSize("group")
+       assert.NotNil(t, err)
+       assert.Equal(t, int64(0), result)
+       m.AssertExpectations(t)
+}
\ No newline at end of file