From 135580f96b31e8dd0ccd5695df877a7942b912ae Mon Sep 17 00:00:00 2001 From: Marco Tallskog Date: Fri, 24 May 2019 16:17:02 +0300 Subject: [PATCH] Add support for SDL groups 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 --- bench_test.go | 39 ++++++++ internal/sdlgoredis/sdlgoredis.go | 25 +++++ sdl.go | 86 +++++++++++++---- sdl_test.go | 194 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 326 insertions(+), 18 deletions(-) diff --git a/bench_test.go b/bench_test.go index fa60686..a4b2e13 100644 --- a/bench_test.go +++ b/bench_test.go @@ -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) + } + } + }) + }) + } +} diff --git a/internal/sdlgoredis/sdlgoredis.go b/internal/sdlgoredis/sdlgoredis.go index 1085d2c..b032d8f 100644 --- a/internal/sdlgoredis/sdlgoredis.go +++ b/internal/sdlgoredis/sdlgoredis.go @@ -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 --- 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) } diff --git a/sdl_test.go b/sdl_test.go index a941f83..3d06bb4 100644 --- a/sdl_test.go +++ b/sdl_test.go @@ -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 -- 2.16.6