redis driver for blob cache information and metadb (#2865)

* feat: add redis cache support

https://github.com/project-zot/zot/pull/2005
Fixes https://github.com/project-zot/zot/issues/2004

* feat: add redis cache support

Currently, we have dynamoDB as the remote shared cache but ideal only
for the cloud use case.
For on-prem use case, add support for redis.

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>

* feat(redis): added blackbox tests for redis

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>

* feat(redis): dummy implementation of MetaDB interface for redis cache

Signed-off-by: Alexei Dodon <adodon@cisco.com>

* feat: check validity of driver configuration on metadb instantiation

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat: multiple fixes for redis cache driver implementation

- add missing method GetAllBlobs
- add redis cache tests, with and without mocking

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat(redis): redis implementation for MetaDB

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat(redis): use redsync to block concurrent write access to the redis DB

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat(redis): update .github/workflows/cluster.yaml to also test redis

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat(metadb): add keyPrefix parameter for redis and remove unneeded method meta.Crate()

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat(redis): support RedisCluster configuration and add unit tests

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat(redis): more tests for redis metadb implementation

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat(redis): add more examples and update examples/README.md

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat(redis): move option parsing and redis client initialization under pkg/api/config/redis

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* chore(cachedb): move Cache interface to pkg/storage/types

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat(redis): reorganize code in pkg/storage/cache.go

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat(redis): call redis.SetLogger() with the zot logger as parameter

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

* feat(redis): rename pkg/meta/redisdb to pkg/meta/redis

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>

---------

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>
Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
Signed-off-by: Alexei Dodon <adodon@cisco.com>
Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
Co-authored-by: a <a@tuxpa.in>
Co-authored-by: Ramkumar Chinchani <rchincha@cisco.com>
Co-authored-by: Petu Eusebiu <peusebiu@cisco.com>
Co-authored-by: Alexei Dodon <adodon@cisco.com>
This commit is contained in:
Andrei Aaron
2025-01-30 21:00:52 +02:00
committed by GitHub
parent 90e1393585
commit 05823cd74f
43 changed files with 7886 additions and 442 deletions
+295
View File
@@ -0,0 +1,295 @@
package rediscfg
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/redis/go-redis/v9"
"github.com/spf13/cast"
"zotregistry.dev/zot/errors"
"zotregistry.dev/zot/pkg/log"
)
var once sync.Once //nolint: gochecknoglobals // redis.SetLogger modifies an unprotected global variable
type redisLogger struct {
log log.Logger
}
func (r redisLogger) Printf(ctx context.Context, format string, v ...interface{}) {
r.log.Debug().Msgf(format, v...)
}
func GetRedisClient(redisConfig map[string]interface{}, log log.Logger) (redis.UniversalClient, error) {
once.Do(func() { redis.SetLogger(redisLogger{log}) }) // call redis.SetLogger only once
// go-redis supports connecting via the redis uri specification (more convenient than parameter parsing)
// Note failover/Sentinel cannot be configured via URL parsing at the moment
if val, ok := redisConfig["url"]; ok {
str, ok := val.(string)
if !ok {
return nil, fmt.Errorf("%w: cachedriver %s has invalid value for url", errors.ErrBadConfig, redisConfig)
}
// The cluster URL has additional addresses in query parameters
if strings.Count(str, "addr") > 0 {
opts, err := redis.ParseClusterURL(str)
if err != nil {
return nil, err
}
return redis.NewClusterClient(opts), nil
}
opts, err := redis.ParseURL(str)
if err != nil {
return nil, err
}
return redis.NewClient(opts), nil
}
// URL configuration not provided by the user, we need to initialize UniversalOptions based on the provided parameters
opts := ParseRedisUniversalOptions(redisConfig, log)
return redis.NewUniversalClient(opts), nil
}
func ParseRedisUniversalOptions(redisConfig map[string]interface{}, //nolint: gocyclo
log log.Logger,
) *redis.UniversalOptions {
opts := redis.UniversalOptions{}
sanitizedConfig := map[string]interface{}{}
for key, val := range redisConfig {
if key == "password" || key == "sentinel_password" {
sanitizedConfig[key] = "******"
continue
}
sanitizedConfig[key] = val
}
log.Info().Interface("redisConfig", sanitizedConfig).Msg("parsing redis universal options")
if val, ok := getStringSlice(redisConfig, "addr", log); ok {
opts.Addrs = val
}
if val, ok := getString(redisConfig, "client_name", false, log); ok {
opts.ClientName = val
}
if val, ok := getInt(redisConfig, "db", log); ok {
opts.DB = val
}
if val, ok := getInt(redisConfig, "protocol", log); ok {
opts.Protocol = val
}
if val, ok := getString(redisConfig, "username", false, log); ok {
opts.Username = val
}
if val, ok := getString(redisConfig, "password", true, log); ok {
opts.Password = val
}
if val, ok := getString(redisConfig, "sentinel_username", false, log); ok {
opts.SentinelUsername = val
}
if val, ok := getString(redisConfig, "sentinel_password", true, log); ok {
opts.SentinelPassword = val
}
if val, ok := getInt(redisConfig, "max_retries", log); ok {
opts.MaxRetries = val
}
if val, ok := getDuration(redisConfig, "min_retry_backoff", log); ok {
opts.MinRetryBackoff = val
}
if val, ok := getDuration(redisConfig, "max_retry_backoff", log); ok {
opts.MaxRetryBackoff = val
}
if val, ok := getDuration(redisConfig, "dial_timeout", log); ok {
opts.DialTimeout = val
}
if val, ok := getDuration(redisConfig, "read_timeout", log); ok {
opts.ReadTimeout = val
}
if val, ok := getDuration(redisConfig, "write_timeout", log); ok {
opts.WriteTimeout = val
}
if val, ok := getBool(redisConfig, "context_timeout_enabled", log); ok {
opts.ContextTimeoutEnabled = val
}
if val, ok := getBool(redisConfig, "pool_fifo", log); ok {
opts.PoolFIFO = val
}
if val, ok := getInt(redisConfig, "pool_size", log); ok {
opts.PoolSize = val
}
if val, ok := getDuration(redisConfig, "pool_timeout", log); ok {
opts.PoolTimeout = val
}
if val, ok := getInt(redisConfig, "min_idle_conns", log); ok {
opts.MinIdleConns = val
}
if val, ok := getInt(redisConfig, "max_idle_conns", log); ok {
opts.MaxIdleConns = val
}
if val, ok := getInt(redisConfig, "max_active_conns", log); ok {
opts.MaxActiveConns = val
}
if val, ok := getDuration(redisConfig, "conn_max_idle_time", log); ok {
opts.ConnMaxIdleTime = val
}
if val, ok := getDuration(redisConfig, "conn_max_lifetime", log); ok {
opts.ConnMaxLifetime = val
}
if val, ok := getInt(redisConfig, "max_redirects", log); ok {
opts.MaxRedirects = val
}
if val, ok := getBool(redisConfig, "read_only", log); ok {
opts.ReadOnly = val
}
if val, ok := getBool(redisConfig, "route_by_latency", log); ok {
opts.RouteByLatency = val
}
if val, ok := getBool(redisConfig, "route_randomly", log); ok {
opts.RouteRandomly = val
}
if val, ok := getString(redisConfig, "master_name", false, log); ok {
opts.MasterName = val
}
if val, ok := getBool(redisConfig, "disable_identity", log); ok {
opts.DisableIndentity = val
}
if val, ok := getString(redisConfig, "identity_suffix", false, log); ok {
opts.IdentitySuffix = val
}
if val, ok := getBool(redisConfig, "unstable_resp3", log); ok {
opts.UnstableResp3 = val
}
log.Info().Msg("finished parsing redis universal options")
return &opts
}
func logCastWarning(key string, value interface{}, hideValue bool, log log.Logger) {
if hideValue {
log.Warn().Str("key", key).Msg("failed to cast parameter to intended type")
} else {
log.Warn().Str("key", key).Interface("value", value).Msg("failed to cast parameter to intended type")
}
}
func getBool(dict map[string]interface{}, key string, log log.Logger) (bool, bool) {
value, ok := dict[key]
if !ok {
return false, false
}
ret, err := cast.ToBoolE(value)
if err != nil {
logCastWarning(key, value, false, log)
return false, false
}
return ret, true
}
func getInt(dict map[string]interface{}, key string, log log.Logger) (int, bool) {
value, ok := dict[key]
if !ok {
return 0, false
}
ret, err := cast.ToIntE(value)
if err != nil {
logCastWarning(key, value, false, log)
return 0, false
}
return ret, true
}
func getString(dict map[string]interface{}, key string, hideValue bool, log log.Logger) (string, bool) {
value, ok := dict[key]
if !ok {
return "", false
}
ret, err := cast.ToStringE(value)
if err != nil {
logCastWarning(key, value, hideValue, log)
return "", false
}
return ret, true
}
func getStringSlice(dict map[string]interface{}, key string, log log.Logger) ([]string, bool) {
value, ok := dict[key]
if !ok {
return []string{}, false
}
ret, err := cast.ToStringSliceE(value)
if err != nil {
logCastWarning(key, value, false, log)
return []string{}, false
}
return ret, true
}
func getDuration(dict map[string]interface{}, key string, log log.Logger) (time.Duration, bool) {
value, ok := dict[key]
if !ok {
return 0, false
}
ret, err := cast.ToDurationE(value)
if err != nil {
logCastWarning(key, value, false, log)
return 0, false
}
return ret, true
}
+340
View File
@@ -0,0 +1,340 @@
package rediscfg_test
import (
"os"
"path"
"testing"
"time"
"github.com/redis/go-redis/v9"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.dev/zot/pkg/api/config"
rediscfg "zotregistry.dev/zot/pkg/api/config/redis"
"zotregistry.dev/zot/pkg/cli/server"
"zotregistry.dev/zot/pkg/log"
)
func TestRedisOptions(t *testing.T) {
Convey("Test redis initialization", t, func() {
log := log.NewLogger("debug", "")
So(log, ShouldNotBeNil)
Convey("Test redis url parsing", func() {
// Errors
config := map[string]interface{}{"url": false}
clientIntf, err := rediscfg.GetRedisClient(config, log)
So(err, ShouldNotBeNil)
So(clientIntf, ShouldBeNil)
config = map[string]interface{}{"url": ""}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldNotBeNil)
So(clientIntf, ShouldBeNil)
config = map[string]interface{}{"url": "qwerty@localhost:6379/1?dial_timeout=5s"}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldNotBeNil)
So(clientIntf, ShouldBeNil)
config = map[string]interface{}{"url": "http://:qwerty@localhost:6379/1?dial_timeout=5s"}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldNotBeNil)
So(clientIntf, ShouldBeNil)
config = map[string]interface{}{"url": "http://localhost:6379/1?addr=host2:6379&addr=host1:6379"}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldNotBeNil)
So(clientIntf, ShouldBeNil)
// Success
config = map[string]interface{}{"url": "redis://user:password@localhost:6379/1?dial_timeout=5s"}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok := clientIntf.(*redis.Client)
So(ok, ShouldBeTrue)
config = map[string]interface{}{"url": "redis://user:password@host1:6379?addr=host2:6379&addr=host1:6379"}
clientIntf, err = rediscfg.GetRedisClient(config, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok = clientIntf.(*redis.ClusterClient)
So(ok, ShouldBeTrue)
})
Convey("Test empty redis options from struct successfully", func() {
config := map[string]interface{}{}
// All attributes will have zero values
options := rediscfg.ParseRedisUniversalOptions(config, log)
So(options, ShouldNotBeNil)
So(options.Addrs, ShouldEqual, []string(nil))
So(options.DB, ShouldEqual, 0)
So(options.MasterName, ShouldEqual, "")
So(options.ClientName, ShouldEqual, "")
So(options.Protocol, ShouldEqual, 0)
So(options.Username, ShouldEqual, "")
So(options.Password, ShouldEqual, "")
So(options.SentinelUsername, ShouldEqual, "")
So(options.SentinelPassword, ShouldEqual, "")
So(options.DialTimeout, ShouldEqual, 0)
So(options.MaxRetries, ShouldEqual, 0)
So(options.MinRetryBackoff, ShouldEqual, 0)
So(options.MaxRetryBackoff, ShouldEqual, 0)
So(options.ReadTimeout, ShouldEqual, 0)
So(options.WriteTimeout, ShouldEqual, 0)
So(options.ContextTimeoutEnabled, ShouldEqual, false)
So(options.PoolFIFO, ShouldEqual, false)
So(options.PoolSize, ShouldEqual, 0)
So(options.PoolTimeout, ShouldEqual, 0)
So(options.MinIdleConns, ShouldEqual, 0)
So(options.MaxIdleConns, ShouldEqual, 0)
So(options.MaxActiveConns, ShouldEqual, 0)
So(options.ConnMaxIdleTime, ShouldEqual, 0)
So(options.ConnMaxLifetime, ShouldEqual, 0)
So(options.MaxRedirects, ShouldEqual, 0)
So(options.ReadOnly, ShouldEqual, false)
So(options.RouteByLatency, ShouldEqual, false)
So(options.RouteRandomly, ShouldEqual, false)
So(options.DisableIndentity, ShouldEqual, false)
So(options.IdentitySuffix, ShouldEqual, "")
So(options.UnstableResp3, ShouldEqual, false)
clientIntf, err := rediscfg.GetRedisClient(config, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok := clientIntf.(*redis.Client)
So(ok, ShouldBeTrue)
})
Convey("Test redis options from struct successfully", func() {
config := map[string]interface{}{
"addr": []string{
"a.repo:26379",
"b.repo:26379",
"c.repo:26379",
},
"db": 1,
"master_name": "zotmeta",
"client_name": "client",
"protocol": 3,
"username": "redis",
"password": "**secret**",
"sentinel_username": "sentinel",
"sentinel_password": "**secret**",
"dial_timeout": 5 * time.Second,
"max_retries": 5,
"min_retry_backoff": 1 * time.Second,
"max_retry_backoff": 3 * time.Second,
"read_timeout": 1 * time.Second,
"write_timeout": 1 * time.Second,
"context_timeout_enabled": true,
"pool_fifo": false,
"pool_size": 2,
"pool_timeout": 10 * time.Second,
"min_idle_conns": 1,
"max_idle_conns": 2,
"max_active_conns": 3,
"conn_max_idle_time": 20 * time.Second,
"conn_max_lifetime": 50 * time.Second,
"max_redirects": 3,
"read_only": true,
"route_by_latency": false,
"route_randomly": true,
"disable_identity": false,
"identity_suffix": "test",
"unstable_resp3": true,
}
// All attribute values are taken from config
options := rediscfg.ParseRedisUniversalOptions(config, log)
So(options, ShouldNotBeNil)
So(options.Addrs, ShouldEqual, []string{"a.repo:26379", "b.repo:26379", "c.repo:26379"})
So(options.DB, ShouldEqual, 1)
So(options.MasterName, ShouldEqual, "zotmeta")
So(options.ClientName, ShouldEqual, "client")
So(options.Protocol, ShouldEqual, 3)
So(options.Username, ShouldEqual, "redis")
So(options.Password, ShouldEqual, "**secret**")
So(options.SentinelUsername, ShouldEqual, "sentinel")
So(options.SentinelPassword, ShouldEqual, "**secret**")
So(options.DialTimeout, ShouldEqual, 5*time.Second)
So(options.MaxRetries, ShouldEqual, 5)
So(options.MinRetryBackoff, ShouldEqual, 1*time.Second)
So(options.MaxRetryBackoff, ShouldEqual, 3*time.Second)
So(options.ReadTimeout, ShouldEqual, 1*time.Second)
So(options.WriteTimeout, ShouldEqual, 1*time.Second)
So(options.ContextTimeoutEnabled, ShouldEqual, true)
So(options.PoolFIFO, ShouldEqual, false)
So(options.PoolSize, ShouldEqual, 2)
So(options.PoolTimeout, ShouldEqual, 10*time.Second)
So(options.MinIdleConns, ShouldEqual, 1)
So(options.MaxIdleConns, ShouldEqual, 2)
So(options.MaxActiveConns, ShouldEqual, 3)
So(options.ConnMaxIdleTime, ShouldEqual, 20*time.Second)
So(options.ConnMaxLifetime, ShouldEqual, 50*time.Second)
So(options.MaxRedirects, ShouldEqual, 3)
So(options.ReadOnly, ShouldEqual, true)
So(options.RouteByLatency, ShouldEqual, false)
So(options.RouteRandomly, ShouldEqual, true)
So(options.DisableIndentity, ShouldEqual, false)
So(options.IdentitySuffix, ShouldEqual, "test")
So(options.UnstableResp3, ShouldEqual, true)
clientIntf, err := rediscfg.GetRedisClient(config, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok := clientIntf.(*redis.Client)
So(ok, ShouldBeTrue)
})
Convey("Test redis options from struct with warnings", func() {
config := map[string]interface{}{
"addr": map[string]int{},
"db": "somestring",
"master_name": map[string]int{},
"client_name": map[string]int{},
"protocol": "somestring",
"username": map[string]int{},
"password": map[string]int{},
"sentinel_username": map[string]int{},
"sentinel_password": map[string]int{},
"dial_timeout": "somestring",
"max_retries": "somestring",
"min_retry_backoff": "somestring",
"max_retry_backoff": "somestring",
"read_timeout": false,
"write_timeout": true,
"context_timeout_enabled": "somestring",
"pool_fifo": "somestring",
"pool_size": "somestring",
"pool_timeout": "somestring",
"min_idle_conns": map[string]int{},
"max_idle_conns": map[string]int{},
"max_active_conns": "somestring",
"conn_max_idle_time": "somestring",
"conn_max_lifetime": "somestring",
"max_redirects": map[string]int{},
"read_only": map[string]int{},
"route_by_latency": "somestring",
"route_randomly": map[string]int{},
"disable_identity": "somestring",
"identity_suffix": map[string]int{},
"unstable_resp3": "somestring",
}
// All attributes remain with default values
options := rediscfg.ParseRedisUniversalOptions(config, log)
So(options, ShouldNotBeNil)
So(options.Addrs, ShouldEqual, []string(nil))
So(options.DB, ShouldEqual, 0)
So(options.MasterName, ShouldEqual, "")
So(options.ClientName, ShouldEqual, "")
So(options.Protocol, ShouldEqual, 0)
So(options.Username, ShouldEqual, "")
So(options.Password, ShouldEqual, "")
So(options.SentinelUsername, ShouldEqual, "")
So(options.SentinelPassword, ShouldEqual, "")
So(options.DialTimeout, ShouldEqual, 0)
So(options.MaxRetries, ShouldEqual, 0)
So(options.MinRetryBackoff, ShouldEqual, 0)
So(options.MaxRetryBackoff, ShouldEqual, 0)
So(options.ReadTimeout, ShouldEqual, 0)
So(options.WriteTimeout, ShouldEqual, 0)
So(options.ContextTimeoutEnabled, ShouldEqual, false)
So(options.PoolFIFO, ShouldEqual, false)
So(options.PoolSize, ShouldEqual, 0)
So(options.PoolTimeout, ShouldEqual, 0)
So(options.MinIdleConns, ShouldEqual, 0)
So(options.MaxIdleConns, ShouldEqual, 0)
So(options.MaxActiveConns, ShouldEqual, 0)
So(options.ConnMaxIdleTime, ShouldEqual, 0)
So(options.ConnMaxLifetime, ShouldEqual, 0)
So(options.MaxRedirects, ShouldEqual, 0)
So(options.ReadOnly, ShouldEqual, false)
So(options.RouteByLatency, ShouldEqual, false)
So(options.RouteRandomly, ShouldEqual, false)
So(options.DisableIndentity, ShouldEqual, false)
So(options.IdentitySuffix, ShouldEqual, "")
So(options.UnstableResp3, ShouldEqual, false)
clientIntf, err := rediscfg.GetRedisClient(config, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok := clientIntf.(*redis.Client)
So(ok, ShouldBeTrue)
})
Convey("Test redis options from json", func(c C) {
fileContent := []byte(`{
"distSpecVersion": "1.1.0",
"storage": {
"remoteCache": true,
"cacheDriver": {
"name": "redis",
"addr": [
"a.repo:26379",
"b.repo:26379",
"c.repo:26379"
],
"db": 1,
"master_name": "zotmeta",
"username": "redis",
"password": "**secret**",
"dial_timeout": "5s"
},
"commit": false,
"dedupe": false,
"gc": true,
"rootDirectory": "/data/zot-cache/dev"
},
"http": {
"address": "127.0.0.1",
"port": "8080"
},
"log": {
"level": "debug"
}
}`)
dir := t.TempDir()
configPath := path.Join(dir, "test-config.json")
err := os.WriteFile(configPath, fileContent, 0o600)
So(err, ShouldBeNil)
conf := config.New()
err = server.LoadConfiguration(conf, configPath)
So(err, ShouldBeNil)
options := rediscfg.ParseRedisUniversalOptions(conf.Storage.CacheDriver, log)
So(options, ShouldNotBeNil)
So(options.Addrs, ShouldEqual, []string{"a.repo:26379", "b.repo:26379", "c.repo:26379"})
So(options.DB, ShouldEqual, 1)
So(options.MasterName, ShouldEqual, "zotmeta")
So(options.Username, ShouldEqual, "redis")
So(options.Password, ShouldEqual, "**secret**")
So(options.DialTimeout, ShouldEqual, 5*time.Second)
clientIntf, err := rediscfg.GetRedisClient(conf.Storage.CacheDriver, log)
So(err, ShouldBeNil)
So(clientIntf, ShouldNotBeNil)
_, ok := clientIntf.(*redis.Client)
So(ok, ShouldBeTrue)
})
})
}
+122 -4
View File
@@ -26,6 +26,7 @@ import (
"testing"
"time"
"github.com/alicebob/miniredis/v2"
"github.com/google/go-github/v62/github"
"github.com/gorilla/mux"
"github.com/gorilla/securecookie"
@@ -154,6 +155,55 @@ func TestCreateCacheDatabaseDriver(t *testing.T) {
So(err, ShouldBeNil)
So(driver, ShouldBeNil)
})
Convey("Test CreateCacheDatabaseDriver redisdb", t, func() {
miniRedis := miniredis.RunT(t)
log := log.NewLogger("debug", "")
dir := t.TempDir()
conf := config.New()
conf.Storage.RootDirectory = dir
conf.Storage.Dedupe = true
conf.Storage.RemoteCache = true
// test error on invalid redis client config
conf.Storage.CacheDriver = map[string]interface{}{
"name": "redis",
"url": false,
}
driver, err := storage.CreateCacheDatabaseDriver(conf.Storage.StorageConfig, log)
So(err, ShouldNotBeNil)
So(driver, ShouldBeNil)
// test valid redis client config
conf.Storage.CacheDriver = map[string]interface{}{
"name": "redis",
"url": "redis://" + miniRedis.Addr(),
}
// test initialization for S3 storage
conf.Storage.StorageDriver = map[string]interface{}{
"name": "s3",
"rootdirectory": "/zot",
"url": "us-east-2",
}
driver, err = storage.CreateCacheDatabaseDriver(conf.Storage.StorageConfig, log)
So(err, ShouldBeNil)
So(driver, ShouldNotBeNil)
So(driver.Name(), ShouldEqual, "redis")
So(driver.UsesRelativePaths(), ShouldEqual, false)
// test initialization for local storage
conf.Storage.StorageDriver = nil
driver, err = storage.CreateCacheDatabaseDriver(conf.Storage.StorageConfig, log)
So(err, ShouldBeNil)
So(driver, ShouldNotBeNil)
So(driver.Name(), ShouldEqual, "redis")
So(driver.UsesRelativePaths(), ShouldEqual, true)
})
tskip.SkipDynamo(t)
tskip.SkipS3(t)
Convey("Test CreateCacheDatabaseDriver dynamodb", t, func() {
@@ -226,7 +276,7 @@ func TestCreateCacheDatabaseDriver(t *testing.T) {
}
func TestCreateMetaDBDriver(t *testing.T) {
Convey("Test CreateCacheDatabaseDriver dynamo", t, func() {
Convey("Test create MetaDB dynamo", t, func() {
log := log.NewLogger("debug", "")
dir := t.TempDir()
conf := config.New()
@@ -253,11 +303,26 @@ func TestCreateMetaDBDriver(t *testing.T) {
"userdatatablename": "UserDatatable",
}
metaDB, err := meta.New(conf.Storage.StorageConfig, log)
So(err, ShouldNotBeNil)
So(metaDB, ShouldBeNil)
conf.Storage.CacheDriver = map[string]interface{}{
"name": "dynamodb",
"endpoint": "http://localhost:4566",
"region": "us-east-2",
"cachetablename": "BlobTable",
"repometatablename": "RepoMetadataTable",
"imageMetaTablename": "ZotImageMetaTable",
"repoBlobsInfoTablename": "ZotRepoBlobsInfoTable",
"userdatatablename": "UserDatatable",
}
testFunc := func() { _, _ = meta.New(conf.Storage.StorageConfig, log) }
So(testFunc, ShouldPanic)
conf.Storage.CacheDriver = map[string]interface{}{
"name": "dummy",
"name": "dynamodb",
"endpoint": "http://localhost:4566",
"region": "us-east-2",
"cachetablename": "",
@@ -272,7 +337,7 @@ func TestCreateMetaDBDriver(t *testing.T) {
So(testFunc, ShouldPanic)
conf.Storage.CacheDriver = map[string]interface{}{
"name": "dummy",
"name": "dynamodb",
"endpoint": "http://localhost:4566",
"region": "us-east-2",
"cachetablename": "test",
@@ -288,7 +353,60 @@ func TestCreateMetaDBDriver(t *testing.T) {
So(testFunc, ShouldNotPanic)
})
Convey("Test CreateCacheDatabaseDriver bolt", t, func() {
Convey("Test create MetaDB redis", t, func() {
miniRedis := miniredis.RunT(t)
log := log.NewLogger("debug", "")
dir := t.TempDir()
conf := config.New()
conf.Storage.RootDirectory = dir
conf.Storage.Dedupe = true
conf.Storage.RemoteCache = true
conf.Storage.StorageDriver = map[string]interface{}{
"name": "s3",
"rootdirectory": "/zot",
"region": "us-east-2",
"bucket": "zot-storage",
"secure": true,
"skipverify": false,
}
conf.Storage.CacheDriver = map[string]interface{}{
"name": "dummy",
}
metaDB, err := meta.New(conf.Storage.StorageConfig, log)
So(err, ShouldNotBeNil)
So(metaDB, ShouldBeNil)
conf.Storage.CacheDriver = map[string]interface{}{
"name": "redis",
}
metaDB, err = meta.New(conf.Storage.StorageConfig, log)
So(err, ShouldNotBeNil)
So(metaDB, ShouldBeNil)
conf.Storage.CacheDriver = map[string]interface{}{
"name": "redis",
"url": "url",
}
metaDB, err = meta.New(conf.Storage.StorageConfig, log)
So(err, ShouldNotBeNil)
So(metaDB, ShouldBeNil)
conf.Storage.CacheDriver = map[string]interface{}{
"name": "redis",
"url": "redis://" + miniRedis.Addr(),
}
metaDB, err = meta.New(conf.Storage.StorageConfig, log)
So(err, ShouldBeNil)
So(metaDB, ShouldNotBeNil)
})
Convey("Test create MetaDB bolt", t, func() {
log := log.NewLogger("debug", "")
dir := t.TempDir()
conf := config.New()