Skip to content

Commit

Permalink
Olric provider embedded mode support (#80)
Browse files Browse the repository at this point in the history
* Implement Olric support as embedded mode

* Remove useless code

* Fix test env containers

* Update embeddedOlricProvider_test.go

* Support the JSON configuration

* Fix tests

* Support struct to Olric config

* Fix review

* Use UUID for dynamic Olric configuration temp storage

* Fix TTL recovery
  • Loading branch information
darkweak authored May 19, 2021
1 parent 28e1efb commit bf77b8e
Show file tree
Hide file tree
Showing 356 changed files with 8,910 additions and 44,252 deletions.
6 changes: 5 additions & 1 deletion cache/providers/abstractProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import (
func InitializeProvider(configuration configurationtypes.AbstractConfigurationInterface) types.AbstractProviderInterface {
var r types.AbstractProviderInterface
if configuration.GetDefaultCache().GetDistributed() {
r, _ = OlricConnectionFactory(configuration)
if configuration.GetDefaultCache().GetOlric().URL != "" {
r, _ = OlricConnectionFactory(configuration)
} else {
r, _ = EmbeddedOlricConnectionFactory(configuration)
}
} else {
r, _ = RistrettoConnectionFactory(configuration)
}
Expand Down
172 changes: 172 additions & 0 deletions cache/providers/embeddedOlricProvider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package providers

import (
"context"
"fmt"
"github.com/buraksezer/olric"
"github.com/buraksezer/olric/config"
"github.com/darkweak/souin/cache/keysaver"
t "github.com/darkweak/souin/configurationtypes"
"github.com/google/uuid"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
"io/ioutil"
"os"
"time"
)

// EmbeddedOlric provider type
type EmbeddedOlric struct {
dm *olric.DMap
db *olric.Olric
keySaver *keysaver.ClearKey
}

func tryToLoadConfiguration(olricInstance *config.Config, olricConfiguration t.CacheProvider, logger *zap.Logger) (*config.Config, bool) {
var e error
isAlreadyLoaded := false
if olricConfiguration.Configuration == nil && olricConfiguration.Path != "" {
if olricInstance, e = config.Load(olricConfiguration.Path); e == nil {
isAlreadyLoaded = true
}
} else if olricConfiguration.Configuration != nil {
tmpFile := "/tmp/" + uuid.NewString() + ".yml"
yamlConfig, e := yaml.Marshal(olricConfiguration.Configuration)
defer func() {
if e = os.RemoveAll(tmpFile); e != nil {
logger.Error("Impossible to remove the temporary file")
}
}()
if e = ioutil.WriteFile(
tmpFile,
yamlConfig,
0644,
); e != nil {
logger.Error("Impossible to create the embedded Olric config from the given one")
}

if olricInstance, e = config.Load(tmpFile); e == nil {
isAlreadyLoaded = true
} else {
logger.Error("Impossible to create the embedded Olric config from the given one")
}
}

return olricInstance, isAlreadyLoaded
}

// EmbeddedOlricConnectionFactory function create new EmbeddedOlric instance
func EmbeddedOlricConnectionFactory(configuration t.AbstractConfigurationInterface) (*EmbeddedOlric, error) {
var keySaver *keysaver.ClearKey
if configuration.GetAPI().Souin.Enable {
keySaver = keysaver.NewClearKey()
}

var olricInstance *config.Config
loaded := false

if olricInstance, loaded = tryToLoadConfiguration(olricInstance, configuration.GetDefaultCache().GetOlric(), configuration.GetLogger()); !loaded {
olricInstance = config.New("local")
olricInstance.Cache.MaxInuse = 512 << 20
}

started, cancel := context.WithCancel(context.Background())
olricInstance.Started = func() {
defer cancel()
configuration.GetLogger().Info("Embedded Olric is ready")
}

db, err := olric.New(olricInstance)
if err != nil {
return nil, err
}

ch := make(chan error, 1)
go func() {
if err = db.Start(); err != nil {
fmt.Println(fmt.Sprintf("Impossible to start the embedded Olric instance: %v", err))
ch <- err
}
}()

select {
case err = <-ch:
return nil, err
case <-started.Done():
}
dm, e := db.NewDMap("souin-map")

return &EmbeddedOlric{
dm,
db,
keySaver,
}, e
}

// ListKeys method returns the list of existing keys
func (provider *EmbeddedOlric) ListKeys() []string {
if nil != provider.keySaver {
return provider.keySaver.ListKeys()
}
return []string{}
}

// Get method returns the populated response if exists, empty response then
func (provider *EmbeddedOlric) Get(key string) []byte {
val2, err := provider.dm.Get(key)

if err != nil {
return []byte{}
}

return val2.([]byte)
}

// Set method will store the response in EmbeddedOlric provider
func (provider *EmbeddedOlric) Set(key string, value []byte, url t.URL, duration time.Duration) {
if duration == 0 {
ttl, err := time.ParseDuration(url.TTL)
if err != nil {
ttl = 0
fmt.Println(err)
}
duration = ttl
}

err := provider.dm.PutEx(key, value, duration)
if err != nil {
panic(err)
} else {
go func() {
if nil != provider.keySaver {
provider.keySaver.AddKey(key)
}
}()
}
}

// Delete method will delete the response in EmbeddedOlric provider if exists corresponding to key param
func (provider *EmbeddedOlric) Delete(key string) {
go func() {
err := provider.dm.Delete(key)
if err != nil {
panic(err)
} else {
go func() {
if nil != provider.keySaver {
provider.keySaver.DelKey(key, 0)
}
}()
}
}()
}

// Init method will initialize EmbeddedOlric provider if needed
func (provider *EmbeddedOlric) Init() error {
return nil
}

// Reset method will reset or close provider
func (provider *EmbeddedOlric) Reset() {
_ = provider.db.Shutdown(context.Background())
}
108 changes: 108 additions & 0 deletions cache/providers/embeddedOlricProvider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package providers

import (
"fmt"
"github.com/darkweak/souin/cache/types"
"github.com/darkweak/souin/configurationtypes"
"github.com/darkweak/souin/errors"
"github.com/darkweak/souin/tests"
"testing"
"time"
)

const EMBEDDEDOLRICVALUE = "My first data"

func mockEmbeddedConfiguration(c func() string, key string) (types.AbstractProviderInterface, configurationtypes.URL) {
return tests.GetCacheProviderClientAndMatchedURL(
key,
func() configurationtypes.AbstractConfigurationInterface {
return tests.MockConfiguration(c)
},
func(config configurationtypes.AbstractConfigurationInterface) (types.AbstractProviderInterface, error) {
provider, _ := EmbeddedOlricConnectionFactory(config)
_ = provider.Init()

return provider, nil
},
)
}

func getEmbeddedOlricClientAndMatchedURL(key string) (types.AbstractProviderInterface, configurationtypes.URL) {
return mockEmbeddedConfiguration(tests.EmbeddedOlricConfiguration, key)
}

func getEmbeddedOlricWithoutYAML(key string) (types.AbstractProviderInterface, configurationtypes.URL) {
return mockEmbeddedConfiguration(tests.EmbeddedOlricPlainConfigurationWithoutAdditionalYAML, key)
}

func TestIShouldBeAbleToReadAndWriteDataInEmbeddedOlric(t *testing.T) {
client, u := getEmbeddedOlricClientAndMatchedURL("Test")
defer client.Reset()
client.Set("Test", []byte(EMBEDDEDOLRICVALUE), u, time.Duration(10)*time.Second)
time.Sleep(3 * time.Second)
res := client.Get("Test")
if EMBEDDEDOLRICVALUE != string(res) {
errors.GenerateError(t, fmt.Sprintf("%s not corresponding to %s", res, EMBEDDEDOLRICVALUE))
}
}

func TestIShouldBeAbleToReadAndWriteDataInEmbeddedOlricWithoutYAML(t *testing.T) {
client, u := getEmbeddedOlricWithoutYAML("Test")
defer client.Reset()
client.Set("Test", []byte(EMBEDDEDOLRICVALUE), u, time.Duration(10)*time.Second)
time.Sleep(3 * time.Second)
res := client.Get("Test")
if EMBEDDEDOLRICVALUE != string(res) {
errors.GenerateError(t, fmt.Sprintf("%s not corresponding to %s", res, EMBEDDEDOLRICVALUE))
}
}

func TestEmbeddedOlric_GetRequestInCache(t *testing.T) {
client, _ := getEmbeddedOlricClientAndMatchedURL(NONEXISTENTKEY)
defer client.Reset()
res := client.Get(NONEXISTENTKEY)
if string(res) != "" {
errors.GenerateError(t, fmt.Sprintf("Key %s should not exist", NONEXISTENTKEY))
}
}

func TestEmbeddedOlric_SetRequestInCache_OneByte(t *testing.T) {
client, u := getEmbeddedOlricClientAndMatchedURL(BYTEKEY)
defer client.Reset()
client.Set(BYTEKEY, []byte{65}, u, time.Duration(20)*time.Second)
}

func TestEmbeddedOlric_SetRequestInCache_TTL(t *testing.T) {
key := "MyEmptyKey"
client, matchedURL := getEmbeddedOlricClientAndMatchedURL(key)
defer client.Reset()
nv := []byte("Hello world")
setValueThenVerify(client, key, nv, matchedURL, time.Duration(20)*time.Second, t)
}

func TestEmbeddedOlric_SetRequestInCache_NoTTL(t *testing.T) {
client, matchedURL := getEmbeddedOlricClientAndMatchedURL(BYTEKEY)
defer client.Reset()
nv := []byte("New value")
setValueThenVerify(client, BYTEKEY, nv, matchedURL, 0, t)
}

func TestEmbeddedOlric_DeleteRequestInCache(t *testing.T) {
client, _ := getEmbeddedOlricClientAndMatchedURL(BYTEKEY)
defer client.Reset()
client.Delete(BYTEKEY)
time.Sleep(1 * time.Second)
if 0 < len(client.Get(BYTEKEY)) {
errors.GenerateError(t, fmt.Sprintf("Key %s should not exist", BYTEKEY))
}
}

func TestEmbeddedOlric_Init(t *testing.T) {
client, _ := EmbeddedOlricConnectionFactory(tests.MockConfiguration(tests.EmbeddedOlricConfiguration))
err := client.Init()
defer client.Reset()

if nil != err {
errors.GenerateError(t, "Impossible to init EmbeddedOlric provider")
}
}
4 changes: 2 additions & 2 deletions cache/providers/olricProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (provider *Olric) Get(key string) []byte {
return val2.([]byte)
}

// Set method will store the response in Redis provider
// Set method will store the response in Olric provider
func (provider *Olric) Set(key string, value []byte, url t.URL, duration time.Duration) {
if duration == 0 {
ttl, err := time.ParseDuration(url.TTL)
Expand All @@ -84,7 +84,7 @@ func (provider *Olric) Set(key string, value []byte, url t.URL, duration time.Du
}
}

// Delete method will delete the response in Redis provider if exists corresponding to key param
// Delete method will delete the response in Olric provider if exists corresponding to key param
func (provider *Olric) Delete(key string) {
go func() {
err := provider.dm.Delete(key)
Expand Down
4 changes: 3 additions & 1 deletion configurationtypes/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ type URL struct {

//CacheProvider config
type CacheProvider struct {
URL string `yaml:"url"`
URL string `yaml:"url" json:"url"`
Path string `yaml:"path" json:"path"`
Configuration interface{} `yaml:"configuration" json:"configuration"`
}

//DefaultCache configuration
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml.test
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ services:
build:
context: .
target: souin
depends_on:
- olric
ports:
- 80:80
- 443:443
Expand Down
10 changes: 6 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ module github.com/darkweak/souin
go 1.15

require (
github.com/buraksezer/olric v0.3.6
github.com/buraksezer/olric v0.3.7
github.com/dgraph-io/ristretto v0.0.3
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fsnotify/fsnotify v1.4.9
github.com/google/uuid v1.2.0
github.com/pquerna/cachecontrol v0.1.0
go.uber.org/zap v1.16.0
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect
golang.org/x/tools v0.0.0-20200608174601-1b747fd94509 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
Loading

0 comments on commit bf77b8e

Please sign in to comment.