From: Marco Tallskog Date: Wed, 17 Apr 2019 10:19:22 +0000 (+0300) Subject: Initial commit X-Git-Tag: v0.0.1 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=7c17df52f1e8004e68bcc2d339be5d1347625aa6;p=ric-plt%2Fsdlgo.git Initial commit Change-Id: Iaf5456d3f1b8b59a727d7df46fe556fe00ac4c0e Signed-off-by: Marco Tallskog --- diff --git a/LICENSES.txt b/LICENSES.txt new file mode 100644 index 0000000..d5f6563 --- /dev/null +++ b/LICENSES.txt @@ -0,0 +1,29 @@ +Unless otherwise specified, all software contained herein is licensed +under the Apache License, Version 2.0 (the "Software License"); +you may not use this software except in compliance with the Software +License. You may obtain a copy of the Software License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the Software License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the Software License for the specific language governing permissions +and limitations under the Software License. + + + +Unless otherwise specified, all documentation contained herein is licensed +under the Creative Commons License, Attribution 4.0 Intl. (the +"Documentation License"); you may not use this documentation except in +compliance with the Documentation License. You may obtain a copy of the +Documentation License at + +https://creativecommons.org/licenses/by/4.0/ + +Unless required by applicable law or agreed to in writing, documentation +distributed under the Documentation License is distributed on an "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the Documentation License for the specific language governing +permissions and limitations under the Documentation License. + diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/bench_test.go b/bench_test.go new file mode 100644 index 0000000..233f128 --- /dev/null +++ b/bench_test.go @@ -0,0 +1,190 @@ +/* + Copyright (c) 2019 AT&T Intellectual Property. + Copyright (c) 2018-2019 Nokia. + + 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 sdlgo_test + +import ( + "fmt" + "strconv" + "strings" + "testing" + + "gerrit.oran-osc.org/r/ric-plt/sdlgo" +) + +type singleBenchmark struct { + key string + keySize int + valueSize int +} + +type multiBenchmark struct { + keyBase string + keyCount int + keySize int + valueSize int +} + +func (bm singleBenchmark) String(oper string) string { + return fmt.Sprintf("op = %s key=%d value=%d", oper, bm.keySize, bm.valueSize) +} + +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 BenchmarkSet(b *testing.B) { + benchmarks := []singleBenchmark{ + {"a", 10, 64}, + {"b", 10, 1024}, + {"c", 10, 64 * 1024}, + {"d", 10, 1024 * 1024}, + {"e", 10, 10 * 1024 * 1024}, + + {"f", 100, 64}, + {"g", 100, 1024}, + {"h", 100, 64 * 1024}, + {"i", 100, 1024 * 1024}, + {"j", 100, 10 * 1024 * 1024}, + } + + for _, bm := range benchmarks { + b.Run(bm.String("set"), func(b *testing.B) { + key := strings.Repeat(bm.key, bm.keySize) + value := strings.Repeat("1", bm.valueSize) + sdl := sdlgo.NewSdlInstance("namespace", sdlgo.NewDatabase()) + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + err := sdl.Set(key, value) + if err != nil { + b.Fatal(err) + } + } + }) + }) + } +} + +func BenchmarkGet(b *testing.B) { + benchmarks := []singleBenchmark{ + {"a", 10, 64}, + {"b", 10, 1024}, + {"c", 10, 64 * 1024}, + {"d", 10, 1024 * 1024}, + {"e", 10, 10 * 1024 * 1024}, + + {"f", 100, 64}, + {"g", 100, 1024}, + {"h", 100, 64 * 1024}, + {"i", 100, 1024 * 1024}, + {"j", 100, 10 * 1024 * 1024}, + } + + for _, bm := range benchmarks { + b.Run(bm.String("Get"), func(b *testing.B) { + key := strings.Repeat(bm.key, bm.keySize) + value := strings.Repeat("1", bm.valueSize) + sdl := sdlgo.NewSdlInstance("namespace", sdlgo.NewDatabase()) + if err := sdl.Set(key, value); err != nil { + b.Fatal(err) + } + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, err := sdl.Get([]string{key}) + if err != nil { + b.Fatal(err) + } + } + }) + }) + } +} + +func BenchmarkMultiSet(b *testing.B) { + benchmarks := []multiBenchmark{ + {"a", 2, 10, 64}, + {"b", 10, 10, 64}, + {"c", 100, 10, 64}, + {"d", 1000, 10, 64}, + {"e", 5000, 10, 64}, + + {"f", 2, 100, 64}, + {"g", 10, 100, 64}, + {"h", 100, 100, 64}, + {"i", 1000, 100, 64}, + {"j", 5000, 100, 64}, + } + + for _, bm := range benchmarks { + b.Run(bm.String("mset"), func(b *testing.B) { + sdl := sdlgo.NewSdlInstance("namespace", sdlgo.NewDatabase()) + value := strings.Repeat("1", bm.valueSize) + keyVals := make([]string, 0) + for i := 0; i < bm.keyCount; i++ { + key := strings.Repeat(bm.keyBase+strconv.Itoa(i), bm.keySize) + keyVals = append(keyVals, key, value) + } + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + err := sdl.Set(keyVals) + if err != nil { + b.Fatal(err) + } + } + }) + }) + } +} + +func BenchmarkMultiGet(b *testing.B) { + benchmarks := []multiBenchmark{ + {"a", 2, 10, 64}, + {"b", 10, 10, 64}, + {"c", 100, 10, 64}, + {"d", 1000, 10, 64}, + {"e", 5000, 10, 64}, + + {"f", 2, 100, 64}, + {"g", 10, 100, 64}, + {"h", 100, 100, 64}, + {"i", 1000, 100, 64}, + {"j", 5000, 100, 64}, + } + + for _, bm := range benchmarks { + b.Run(bm.String("gset"), func(b *testing.B) { + sdl := sdlgo.NewSdlInstance("namespace", sdlgo.NewDatabase()) + keyVals := make([]string, 0) + for i := 0; i < bm.keyCount; i++ { + key := strings.Repeat(bm.keyBase+strconv.Itoa(i), bm.keySize) + keyVals = append(keyVals, key) + } + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, err := sdl.Get(keyVals) + if err != nil { + b.Fatal(err) + } + } + }) + }) + } +} diff --git a/cmd/sdltester/sdltester.go b/cmd/sdltester/sdltester.go new file mode 100644 index 0000000..85e5569 --- /dev/null +++ b/cmd/sdltester/sdltester.go @@ -0,0 +1,285 @@ +/* + Copyright (c) 2019 AT&T Intellectual Property. + Copyright (c) 2018-2019 Nokia. + + 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 main + +import ( + "fmt" + "os" + "time" + + "gerrit.oran-osc.org/r/ric-plt/sdlgo" +) + +/* + * This program demonsrates the basic usage of sdlgo module. + * + * The following scenarios are provided: + * + * - write: Write data. Performance is measured. + * + * - read: Read data. Performance is measured. + * + * - remove: Remove data. Performance is measured. + * + * - noexist: Read non-existing data. Performance is measured and empty container is returned as nothing was + * found. + * + * - getall: Read all keys within a namespace. One can manually add keys under the used namespace and all + * those keys should be returned here. Performance is measured. + * + * - removeall: Remove all keys within a namespace. Performance is measured. + * + * - emptymap: Write an empty container. Performance is measured. + * + * - multiple: Make two writes. Performance is measured. + * + * - emptydata: Write empty data for a key. Performance is measured. + * + * - writeif and writeifnot: Write if old data (written with the "write" option) has remained and remains + * unchanged during the function call. Do not write if data has changed. Performance + * is measured. + * + * - removeif: Remove if old data (written with the "write" option) has remained and remains + * unchanged during the function call. Do not remove data if data has changed. Performance + * is measured. + */ + +func main() { + sdl := sdlgo.NewSdlInstance("tag1", sdlgo.NewDatabase()) + + if len(os.Args) > 1 { + switch command := os.Args[1]; command { + case "write": + write(sdl) + case "read": + read(sdl) + case "remove": + remove(sdl) + case "noexist": + noexist(sdl) + case "getall": + getall(sdl) + case "removeall": + removeall(sdl) + case "emptymap": + emptymap(sdl) + case "multiple": + multiple(sdl) + case "emptydata": + emptydata(sdl) + case "writeif": + writeif(sdl) + case "writeifnot": + writeifnot(sdl) + case "removeif": + removeif(sdl) + default: + printHelp() + } + + } else { + printHelp() + } +} + +func printHelp() { + fmt.Println("Usage: sdltester ") + fmt.Println("Commands:") + fmt.Println("write Write data. Performance is measured") + fmt.Println("read Read data. Performance is measured") + fmt.Println("remove Remove data. Performance is measured") + fmt.Println("noexist Read non-existing data. Performance is measured and empty container is returned as nothing was") + fmt.Println(" found") + fmt.Println("getall Read all keys within a namespace. One can manually add keys under the used namespace and all") + fmt.Println(" those keys should be returned here. Performance is measured") + fmt.Println("removeall Remove all keys within a namespace. Performance is measured") + fmt.Println("emptymap Write an empty container. Performance is measured") + fmt.Println("multiple Make two writes. Performance is measured") + fmt.Println("emptydata Write empty data for a key. Performance is measured") + fmt.Println("writeif Write if old data (written with the \"write\" option) has remained and remains") + fmt.Println(" unchanged during the function call. Do not write if data has changed. Performance") + fmt.Println(" is measured") + fmt.Println("writeifnot Write only if key is not set. Performance is measured") + fmt.Println("removeif Remove if old data (written with the \"write\" option) has remained and remains") + fmt.Println(" unchanged during the function call. Do not remove data if data has changed. Performance") + fmt.Println(" is measured") +} + +func write(sdl *sdlgo.SdlInstance) { + data := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x44} + start := time.Now() + err := sdl.Set("key1", data) + elapsed := time.Since(start) + if err == nil { + fmt.Printf("Write: %s\n", elapsed) + } else { + fmt.Println(err) + } +} + +func read(sdl *sdlgo.SdlInstance) { + k := []string{"key1"} + start := time.Now() + data, err := sdl.Get(k) + elapsed := time.Since(start) + if err == nil { + value, ok := data["key1"] + if ok && value != nil { + fmt.Printf("Read: %s\n", elapsed) + } else { + fmt.Printf("Read, not found: %s\n", elapsed) + } + + } else { + fmt.Println(err) + } +} + +func remove(sdl *sdlgo.SdlInstance) { + k := []string{"key1"} + start := time.Now() + err := sdl.Remove(k) + elapsed := time.Since(start) + if err == nil { + fmt.Printf("Remove: %s\n", elapsed) + } else { + fmt.Println(err) + } +} + +func noexist(sdl *sdlgo.SdlInstance) { + start := time.Now() + _, err := sdl.Get([]string{"no1", "no2"}) + elapsed := time.Since(start) + if err == nil { + fmt.Printf("Noexist: %s\n", elapsed) + } else { + fmt.Println(err) + } +} + +func getall(sdl *sdlgo.SdlInstance) { + start := time.Now() + keys, err := sdl.GetAll() + elapsed := time.Since(start) + if err == nil { + fmt.Printf("Getall: %s\n", elapsed) + for _, i := range keys { + fmt.Println(i) + } + } else { + fmt.Println(err) + } +} + +func removeall(sdl *sdlgo.SdlInstance) { + start := time.Now() + err := sdl.RemoveAll() + elapsed := time.Since(start) + if err == nil { + fmt.Printf("Removeall: %s\n", elapsed) + } else { + fmt.Println(err) + } +} + +func emptymap(sdl *sdlgo.SdlInstance) { + start := time.Now() + err := sdl.Set("", "") + elapsed := time.Since(start) + if err == nil { + fmt.Printf("Emptymap: %s\n", elapsed) + } else { + fmt.Println(err) + } +} + +func multiple(sdl *sdlgo.SdlInstance) { + data := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x44} + start := time.Now() + err := sdl.Set("key1m", data) + elapsed := time.Since(start) + if err == nil { + fmt.Printf("Multiple: %s ", elapsed) + } else { + fmt.Println(err) + } + start = time.Now() + err = sdl.Set("key2m", data) + elapsed = time.Since(start) + if err == nil { + fmt.Printf(" %s \n", elapsed) + } else { + fmt.Println(err) + } +} + +func emptydata(sdl *sdlgo.SdlInstance) { + data := []byte{} + start := time.Now() + err := sdl.Set("key1", data) + elapsed := time.Since(start) + if err == nil { + fmt.Printf("Emptydata: %s\n", elapsed) + } else { + fmt.Println(err) + } +} + +func writeif(sdl *sdlgo.SdlInstance) { + oldVec := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x44} + newVec := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x66} + start := time.Now() + _, err := sdl.SetIf("key1", oldVec, newVec) + elapsed := time.Since(start) + if err == nil { + fmt.Printf("Writeif: %s\n", elapsed) + } else { + fmt.Println(err) + } +} + +func writeifnot(sdl *sdlgo.SdlInstance) { + vec := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x88} + start := time.Now() + _, err := sdl.SetIfNotExists("key1", vec) + elapsed := time.Since(start) + if err == nil { + fmt.Printf("Writeifnot: %s\n", elapsed) + } else { + fmt.Println(err) + } +} + +func removeif(sdl *sdlgo.SdlInstance) { + vec := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x88} + start := time.Now() + _, err := sdl.RemoveIf("key1", vec) + elapsed := time.Since(start) + if err == nil { + fmt.Printf("Removeif: %s\n", elapsed) + } else { + fmt.Println(err) + } +} diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..5f78028 --- /dev/null +++ b/example_test.go @@ -0,0 +1,49 @@ +/* + Copyright (c) 2019 AT&T Intellectual Property. + Copyright (c) 2018-2019 Nokia. + + 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 sdlgo_test + +import ( + "fmt" + + "gerrit.oran-osc.org/r/ric-plt/sdlgo" +) + +var sdl *sdlgo.SdlInstance + +func init() { + sdl = sdlgo.NewSdlInstance("namespace", sdlgo.NewDatabase()) +} + +func ExampleSdlInstance_Set() { + err := sdl.Set("stringdata", "data", "intdata", 42) + if err != nil { + panic(err) + } else { + fmt.Println("Data written successfully") + } + // Output: Data written successfully +} + +func ExampleSdlInstance_Get() { + retMap, err := sdl.Get([]string{"strigdata", "intdata"}) + if err != nil { + panic(err) + } else { + fmt.Println(retMap) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d46db31 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module gerrit.oran-osc.org/r/ric-plt/sdlgo + +go 1.12 + +require ( + github.com/go-redis/redis v6.15.2+incompatible + github.com/onsi/ginkgo v1.8.0 // indirect + github.com/onsi/gomega v1.5.0 // indirect + github.com/stretchr/testify v1.3.0 +) + +replace gerrit.oran-osc.org/r/ric-plt/sdlgo/internal/sdlgoredis => ./internal/sdlgoredis diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a6e2f77 --- /dev/null +++ b/go.sum @@ -0,0 +1,37 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= +github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/sdlgoredis/sdlgoredis.go b/internal/sdlgoredis/sdlgoredis.go new file mode 100644 index 0000000..01fe5f7 --- /dev/null +++ b/internal/sdlgoredis/sdlgoredis.go @@ -0,0 +1,126 @@ +/* + Copyright (c) 2019 AT&T Intellectual Property. + Copyright (c) 2018-2019 Nokia. + + 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 sdlgoredis + +import ( + "errors" + "fmt" + "os" + + "github.com/go-redis/redis" +) + +type DB struct { + client *redis.Client + redisModules bool +} + +func Create() *DB { + hostname := os.Getenv("DBAAS_SERVICE_HOST") + if hostname == "" { + hostname = "localhost" + } + port := os.Getenv("DBAAS_SERVICE_PORT") + if port == "" { + port = "6379" + } + redisAddress := hostname + ":" + port + client := redis.NewClient(&redis.Options{ + Addr: redisAddress, + Password: "", // no password set + DB: 0, // use default DB + PoolSize: 20, + }) + + db := DB{ + client: client, + redisModules: true, + } + + commands, err := db.client.Command().Result() + if err == nil { + redisModuleCommands := []string{"setie", "delie"} + for _, v := range redisModuleCommands { + _, ok := commands[v] + if !ok { + db.redisModules = false + } + } + } else { + fmt.Println(err) + } + return &db +} + +func (db *DB) Close() error { + return db.client.Close() +} + +func (db *DB) MSet(pairs ...interface{}) error { + return db.client.MSet(pairs...).Err() +} + +func (db *DB) MGet(keys []string) ([]interface{}, error) { + val, err := db.client.MGet(keys...).Result() + return val, err +} + +func (db *DB) Del(keys []string) error { + _, err := db.client.Del(keys...).Result() + return err +} + +func (db *DB) Keys(pattern string) ([]string, error) { + val, err := db.client.Keys(pattern).Result() + return val, err +} + +func (db *DB) SetIE(key string, oldData, newData interface{}) (bool, error) { + if !db.redisModules { + return false, errors.New("Redis deployment not supporting command") + } + + result, err := db.client.Do("SETIE", key, newData, oldData).Result() + if err != nil { + return false, err + } + if result == "OK" { + return true, nil + } else { + return false, nil + } +} + +func (db *DB) SetNX(key string, data interface{}) (bool, error) { + result, err := db.client.SetNX(key, data, 0).Result() + return result, err +} + +func (db *DB) DelIE(key string, data interface{}) (bool, error) { + if !db.redisModules { + return false, errors.New("Redis deployment not supporting command") + } + result, err := db.client.Do("DELIE", key, data).Result() + if err != nil { + return false, err + } + if result == "1" { + return true, nil + } + return false, nil +} diff --git a/sdl.go b/sdl.go new file mode 100644 index 0000000..04e0625 --- /dev/null +++ b/sdl.go @@ -0,0 +1,211 @@ +/* + Copyright (c) 2019 AT&T Intellectual Property. + Copyright (c) 2018-2019 Nokia. + + 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 sdlgo + +import ( + "reflect" + "strings" + + "gerrit.oran-osc.org/r/ric-plt/sdlgo/internal/sdlgoredis" +) + +type iDatabase interface { + MSet(pairs ...interface{}) error + MGet(keys []string) ([]interface{}, error) + Close() error + Del(keys []string) error + Keys(key string) ([]string, error) + SetIE(key string, oldData, newData interface{}) (bool, error) + SetNX(key string, data interface{}) (bool, error) + DelIE(key string, data interface{}) (bool, error) +} + +type SdlInstance struct { + nameSpace string + nsPrefix string + iDatabase +} + +//NewDatabase creates a connection to database that will be used +//as a backend for the key-value storage. The returned value shall +//be given as a parameter when calling NewKeyValStorage +func NewDatabase() *sdlgoredis.DB { + db := sdlgoredis.Create() + return db +} + +//NewSdlInstance creates a new sdl instance using the given namespace. +//The database used as a backend is given as a parameter +func NewSdlInstance(NameSpace string, db iDatabase) *SdlInstance { + s := SdlInstance{ + nameSpace: NameSpace, + nsPrefix: "{" + NameSpace + "},", + iDatabase: db, + } + + return &s +} + +func (s *SdlInstance) Close() error { + return s.Close() +} + +func (s *SdlInstance) setNamespaceToKeys(pairs ...interface{}) []interface{} { + var retVal []interface{} + for i, v := range pairs { + if i%2 == 0 { + reflectType := reflect.TypeOf(v) + switch reflectType.Kind() { + case reflect.Slice: + x := reflect.ValueOf(v) + for i2 := 0; i2 < x.Len(); i2++ { + if i2%2 == 0 { + retVal = append(retVal, s.nsPrefix+x.Index(i2).Interface().(string)) + } else { + retVal = append(retVal, x.Index(i2).Interface()) + } + } + case reflect.Array: + x := reflect.ValueOf(v) + for i2 := 0; i2 < x.Len(); i2++ { + if i2%2 == 0 { + retVal = append(retVal, s.nsPrefix+x.Index(i2).Interface().(string)) + } else { + retVal = append(retVal, x.Index(i2).Interface()) + } + } + default: + retVal = append(retVal, s.nsPrefix+v.(string)) + } + } else { + retVal = append(retVal, v) + } + } + return retVal +} + +//Set function writes data to shared data layer storage. Writing is done +//atomically, i.e. all succeeds or fails. +//Data to be written is given as key-value pairs. Several key-value +//pairs can be written with one call. +//The key is expected to be string whereas value can be anything, string, +//number, slice array or map +func (s *SdlInstance) Set(pairs ...interface{}) error { + if len(pairs) == 0 { + return nil + } + + keyAndData := s.setNamespaceToKeys(pairs...) + err := s.MSet(keyAndData...) + return err +} + +//Get function atomically reads one or more keys from SDL. The returned map has the +//requested keys as index and data as value. If the requested key is not found +//from SDL, it's value is nil +func (s *SdlInstance) Get(keys []string) (map[string]interface{}, error) { + m := make(map[string]interface{}) + if len(keys) == 0 { + return m, nil + } + + var keysWithNs []string + for _, v := range keys { + keysWithNs = append(keysWithNs, s.nsPrefix+v) + } + val, err := s.MGet(keysWithNs) + if err != nil { + return m, err + } + for i, v := range val { + m[keys[i]] = v + } + return m, err +} + +//SetIf atomically replaces existing data with newData in SDL if data matches the oldData. +//If replace was done successfully, true will be returned. +func (s *SdlInstance) SetIf(key string, oldData, newData interface{}) (bool, error) { + status, err := s.SetIE(s.nsPrefix+key, oldData, newData) + if err != nil { + return false, err + } + return status, nil +} + +//SetIfNotExists conditionally sets the value of a key. If key already exists in SDL, +//then it's value is not changed. Checking the key existence and potential set operation +//is done atomically. +func (s *SdlInstance) SetIfNotExists(key string, data interface{}) (bool, error) { + status, err := s.SetNX(s.nsPrefix+key, data) + if err != nil { + return false, err + } + return status, nil +} + +//Remove data from SDL. Operation is done atomically, i.e. either all succeeds or fails +func (s *SdlInstance) Remove(keys []string) error { + if len(keys) == 0 { + return nil + } + + var keysWithNs []string + for _, v := range keys { + keysWithNs = append(keysWithNs, s.nsPrefix+v) + } + err := s.Del(keysWithNs) + return err +} + +//RemoveIf removes data from SDL conditionally. If existing data matches given data, +//key and data are removed from SDL. If remove was done successfully, true is returned. +func (s *SdlInstance) RemoveIf(key string, data interface{}) (bool, error) { + status, err := s.DelIE(s.nsPrefix+key, data) + if err != nil { + return false, err + } + return status, nil +} + +//GetAll returns all keys under the namespace. No prior knowledge about the keys in the +//given namespace exists, thus operation is not guaranteed to be atomic or isolated. +func (s *SdlInstance) GetAll() ([]string, error) { + keys, err := s.Keys(s.nsPrefix + "*") + var retVal []string = nil + if err != nil { + return retVal, err + } + for _, v := range keys { + retVal = append(retVal, strings.Split(v, s.nsPrefix)[1]) + } + return retVal, err +} + +//RemoveAll removes all keys under the namespace. Remove operation is not atomic, thus +//it is not guaranteed that all keys are removed. +func (s *SdlInstance) RemoveAll() error { + keys, err := s.Keys(s.nsPrefix + "*") + if err != nil { + return err + } + if keys != nil { + err = s.Del(keys) + } + return err +} diff --git a/sdl_test.go b/sdl_test.go new file mode 100644 index 0000000..75ee321 --- /dev/null +++ b/sdl_test.go @@ -0,0 +1,435 @@ +/* + Copyright (c) 2019 AT&T Intellectual Property. + Copyright (c) 2018-2019 Nokia. + + 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 sdlgo_test + +import ( + "errors" + "testing" + + "gerrit.oran-osc.org/r/ric-plt/sdlgo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type mockDB struct { + mock.Mock +} + +func (m *mockDB) MSet(pairs ...interface{}) error { + a := m.Called(pairs) + return a.Error(0) +} + +func (m *mockDB) MGet(keys []string) ([]interface{}, error) { + a := m.Called(keys) + return a.Get(0).([]interface{}), a.Error(1) +} + +func (m *mockDB) Close() error { + a := m.Called() + return a.Error(0) +} + +func (m *mockDB) Del(keys []string) error { + a := m.Called(keys) + return a.Error(0) +} + +func (m *mockDB) Keys(pattern string) ([]string, error) { + a := m.Called(pattern) + return a.Get(0).([]string), a.Error(1) +} + +func (m *mockDB) SetIE(key string, oldData, newData interface{}) (bool, error) { + a := m.Called(key, oldData, newData) + return a.Bool(0), a.Error(1) +} + +func (m *mockDB) SetNX(key string, data interface{}) (bool, error) { + a := m.Called(key, data) + return a.Bool(0), a.Error(1) +} + +func (m *mockDB) DelIE(key string, data interface{}) (bool, error) { + a := m.Called(key, data) + return a.Bool(0), a.Error(1) +} + +func setup() (*mockDB, *sdlgo.SdlInstance) { + m := new(mockDB) + i := sdlgo.NewSdlInstance("namespace", m) + return m, i +} + +func TestGetOneKey(t *testing.T) { + m, i := setup() + + mgetExpected := []string{"{namespace},key"} + mReturn := []interface{}{"somevalue"} + mReturnExpected := make(map[string]interface{}) + mReturnExpected["key"] = "somevalue" + + m.On("MGet", mgetExpected).Return(mReturn, nil) + retVal, err := i.Get([]string{"key"}) + assert.Nil(t, err) + assert.Equal(t, mReturnExpected, retVal) + m.AssertExpectations(t) +} + +func TestGetSeveralKeys(t *testing.T) { + m, i := setup() + + mgetExpected := []string{"{namespace},key1", "{namespace},key2", "{namespace},key3"} + mReturn := []interface{}{"somevalue1", 2, "someothervalue"} + mReturnExpected := make(map[string]interface{}) + mReturnExpected["key1"] = "somevalue1" + mReturnExpected["key2"] = 2 + mReturnExpected["key3"] = "someothervalue" + + m.On("MGet", mgetExpected).Return(mReturn, nil) + retVal, err := i.Get([]string{"key1", "key2", "key3"}) + assert.Nil(t, err) + assert.Equal(t, mReturnExpected, retVal) + m.AssertExpectations(t) +} + +func TestGetSeveralKeysSomeFail(t *testing.T) { + m, i := setup() + + mgetExpected := []string{"{namespace},key1", "{namespace},key2", "{namespace},key3"} + mReturn := []interface{}{"somevalue1", nil, "someothervalue"} + mReturnExpected := make(map[string]interface{}) + mReturnExpected["key1"] = "somevalue1" + mReturnExpected["key2"] = nil + mReturnExpected["key3"] = "someothervalue" + + m.On("MGet", mgetExpected).Return(mReturn, nil) + retVal, err := i.Get([]string{"key1", "key2", "key3"}) + assert.Nil(t, err) + assert.Equal(t, mReturnExpected, retVal) + m.AssertExpectations(t) +} + +func TestGetKeyReturnError(t *testing.T) { + m, i := setup() + + mgetExpected := []string{"{namespace},key"} + mReturn := []interface{}{nil} + mReturnExpected := make(map[string]interface{}) + + m.On("MGet", mgetExpected).Return(mReturn, errors.New("Some error")) + retVal, err := i.Get([]string{"key"}) + assert.NotNil(t, err) + assert.Equal(t, mReturnExpected, retVal) + m.AssertExpectations(t) +} + +func TestGetEmptyList(t *testing.T) { + m, i := setup() + + mgetExpected := []string{} + + retval, err := i.Get([]string{}) + assert.Nil(t, err) + assert.Len(t, retval, 0) + m.AssertNotCalled(t, "MGet", mgetExpected) +} + +func TestWriteOneKey(t *testing.T) { + m, i := setup() + + msetExpected := []interface{}{"{namespace},key1", "data1"} + + m.On("MSet", msetExpected).Return(nil) + err := i.Set("key1", "data1") + assert.Nil(t, err) + m.AssertExpectations(t) +} + +func TestWriteSeveralKeysSlice(t *testing.T) { + m, i := setup() + + msetExpected := []interface{}{"{namespace},key1", "data1", "{namespace},key2", 22} + + m.On("MSet", msetExpected).Return(nil) + err := i.Set([]interface{}{"key1", "data1", "key2", 22}) + assert.Nil(t, err) + m.AssertExpectations(t) + +} + +func TestWriteSeveralKeysArray(t *testing.T) { + m, i := setup() + + msetExpected := []interface{}{"{namespace},key1", "data1", "{namespace},key2", "data2"} + + m.On("MSet", msetExpected).Return(nil) + err := i.Set([4]string{"key1", "data1", "key2", "data2"}) + assert.Nil(t, err) + m.AssertExpectations(t) +} + +func TestWriteFail(t *testing.T) { + m, i := setup() + + msetExpected := []interface{}{"{namespace},key1", "data1"} + + m.On("MSet", msetExpected).Return(errors.New("Some error")) + err := i.Set("key1", "data1") + assert.NotNil(t, err) + m.AssertExpectations(t) +} + +func TestWriteEmptyList(t *testing.T) { + m, i := setup() + + msetExpected := []interface{}{} + err := i.Set() + assert.Nil(t, err) + m.AssertNotCalled(t, "MSet", msetExpected) +} + +func TestRemoveSuccessfully(t *testing.T) { + m, i := setup() + + msetExpected := []string{"{namespace},key1", "{namespace},key2"} + m.On("Del", msetExpected).Return(nil) + + err := i.Remove([]string{"key1", "key2"}) + assert.Nil(t, err) + m.AssertExpectations(t) +} + +func TestRemoveFail(t *testing.T) { + m, i := setup() + + msetExpected := []string{"{namespace},key"} + m.On("Del", msetExpected).Return(errors.New("Some error")) + + err := i.Remove([]string{"key"}) + assert.NotNil(t, err) + m.AssertExpectations(t) +} + +func TestRemoveEmptyList(t *testing.T) { + m, i := setup() + + err := i.Remove([]string{}) + assert.Nil(t, err) + m.AssertNotCalled(t, "Del", []string{}) +} + +func TestGetAllSuccessfully(t *testing.T) { + m, i := setup() + + mKeysExpected := string("{namespace},*") + mReturnExpected := []string{"{namespace},key1", "{namespace},key2"} + expectedReturn := []string{"key1", "key2"} + m.On("Keys", mKeysExpected).Return(mReturnExpected, nil) + retVal, err := i.GetAll() + assert.Nil(t, err) + assert.Equal(t, expectedReturn, retVal) + m.AssertExpectations(t) +} + +func TestGetAllFail(t *testing.T) { + m, i := setup() + + mKeysExpected := string("{namespace},*") + mReturnExpected := []string{} + m.On("Keys", mKeysExpected).Return(mReturnExpected, errors.New("some error")) + retVal, err := i.GetAll() + assert.NotNil(t, err) + assert.Nil(t, retVal) + assert.Equal(t, len(retVal), 0) + m.AssertExpectations(t) +} + +func TestGetAllReturnEmpty(t *testing.T) { + m, i := setup() + + mKeysExpected := string("{namespace},*") + var mReturnExpected []string = nil + m.On("Keys", mKeysExpected).Return(mReturnExpected, nil) + retVal, err := i.GetAll() + assert.Nil(t, err) + assert.Nil(t, retVal) + assert.Equal(t, len(retVal), 0) + m.AssertExpectations(t) + +} + +func TestRemoveAllSuccessfully(t *testing.T) { + m, i := setup() + + mKeysExpected := string("{namespace},*") + mKeysReturn := []string{"{namespace},key1", "{namespace},key2"} + mDelExpected := mKeysReturn + m.On("Keys", mKeysExpected).Return(mKeysReturn, nil) + m.On("Del", mDelExpected).Return(nil) + err := i.RemoveAll() + assert.Nil(t, err) + m.AssertExpectations(t) +} + +func TestRemoveAllNoKeysFound(t *testing.T) { + m, i := setup() + + mKeysExpected := string("{namespace},*") + var mKeysReturn []string = nil + m.On("Keys", mKeysExpected).Return(mKeysReturn, nil) + m.AssertNumberOfCalls(t, "Del", 0) + err := i.RemoveAll() + assert.Nil(t, err) + m.AssertExpectations(t) +} + +func TestRemoveAllKeysReturnError(t *testing.T) { + m, i := setup() + + mKeysExpected := string("{namespace},*") + var mKeysReturn []string = nil + m.On("Keys", mKeysExpected).Return(mKeysReturn, errors.New("Some error")) + m.AssertNumberOfCalls(t, "Del", 0) + err := i.RemoveAll() + assert.NotNil(t, err) + m.AssertExpectations(t) +} + +func TestRemoveAllDelReturnError(t *testing.T) { + m, i := setup() + + mKeysExpected := string("{namespace},*") + mKeysReturn := []string{"{namespace},key1", "{namespace},key2"} + mDelExpected := mKeysReturn + m.On("Keys", mKeysExpected).Return(mKeysReturn, nil) + m.On("Del", mDelExpected).Return(errors.New("Some Error")) + err := i.RemoveAll() + assert.NotNil(t, err) + m.AssertExpectations(t) +} + +func TestSetIfSuccessfullyOkStatus(t *testing.T) { + m, i := setup() + + mSetIEExpectedKey := string("{namespace},key1") + mSetIEExpectedOldData := interface{}("olddata") + mSetIEExpectedNewData := interface{}("newdata") + m.On("SetIE", mSetIEExpectedKey, mSetIEExpectedOldData, mSetIEExpectedNewData).Return(true, nil) + status, err := i.SetIf("key1", "olddata", "newdata") + assert.Nil(t, err) + assert.True(t, status) + m.AssertExpectations(t) +} + +func TestSetIfSuccessfullyNOKStatus(t *testing.T) { + m, i := setup() + + mSetIEExpectedKey := string("{namespace},key1") + mSetIEExpectedOldData := interface{}("olddata") + mSetIEExpectedNewData := interface{}("newdata") + m.On("SetIE", mSetIEExpectedKey, mSetIEExpectedOldData, mSetIEExpectedNewData).Return(false, nil) + status, err := i.SetIf("key1", "olddata", "newdata") + assert.Nil(t, err) + assert.False(t, status) + m.AssertExpectations(t) +} + +func TestSetIfFailure(t *testing.T) { + m, i := setup() + + mSetIEExpectedKey := string("{namespace},key1") + mSetIEExpectedOldData := interface{}("olddata") + mSetIEExpectedNewData := interface{}("newdata") + m.On("SetIE", mSetIEExpectedKey, mSetIEExpectedOldData, mSetIEExpectedNewData).Return(true, errors.New("Some error")) + status, err := i.SetIf("key1", "olddata", "newdata") + assert.NotNil(t, err) + assert.False(t, status) + m.AssertExpectations(t) +} + +func TestSetIfNotExistsSuccessfullyOkStatus(t *testing.T) { + m, i := setup() + + mSetNXExpectedKey := string("{namespace},key1") + mSetNXExpectedData := interface{}("data") + m.On("SetNX", mSetNXExpectedKey, mSetNXExpectedData).Return(true, nil) + status, err := i.SetIfNotExists("key1", "data") + assert.Nil(t, err) + assert.True(t, status) + m.AssertExpectations(t) +} + +func TestSetIfNotExistsSuccessfullyNOKStatus(t *testing.T) { + m, i := setup() + + mSetNXExpectedKey := string("{namespace},key1") + mSetNXExpectedData := interface{}("data") + m.On("SetNX", mSetNXExpectedKey, mSetNXExpectedData).Return(false, nil) + status, err := i.SetIfNotExists("key1", "data") + assert.Nil(t, err) + assert.False(t, status) + m.AssertExpectations(t) +} + +func TestSetIfNotExistsFailure(t *testing.T) { + m, i := setup() + + mSetNXExpectedKey := string("{namespace},key1") + mSetNXExpectedData := interface{}("data") + m.On("SetNX", mSetNXExpectedKey, mSetNXExpectedData).Return(true, errors.New("Some error")) + status, err := i.SetIfNotExists("key1", "data") + assert.NotNil(t, err) + assert.False(t, status) + m.AssertExpectations(t) +} +func TestRemoveIfSuccessfullyOkStatus(t *testing.T) { + m, i := setup() + + mDelIEExpectedKey := string("{namespace},key1") + mDelIEExpectedData := interface{}("data") + m.On("DelIE", mDelIEExpectedKey, mDelIEExpectedData).Return(true, nil) + status, err := i.RemoveIf("key1", "data") + assert.Nil(t, err) + assert.True(t, status) + m.AssertExpectations(t) +} + +func TestRemoveIfSuccessfullyNOKStatus(t *testing.T) { + m, i := setup() + + mDelIEExpectedKey := string("{namespace},key1") + mDelIEExpectedData := interface{}("data") + m.On("DelIE", mDelIEExpectedKey, mDelIEExpectedData).Return(false, nil) + status, err := i.RemoveIf("key1", "data") + assert.Nil(t, err) + assert.False(t, status) + m.AssertExpectations(t) +} + +func TestRemoveIfFailure(t *testing.T) { + m, i := setup() + + mDelIEExpectedKey := string("{namespace},key1") + mDelIEExpectedData := interface{}("data") + m.On("DelIE", mDelIEExpectedKey, mDelIEExpectedData).Return(true, errors.New("Some error")) + status, err := i.RemoveIf("key1", "data") + assert.NotNil(t, err) + assert.False(t, status) + m.AssertExpectations(t) +}