chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
This commit is contained in:
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
rclient "github.com/go-redis/redis/v8"
|
||||
"go-micro.dev/v6/cache"
|
||||
)
|
||||
|
||||
type redisOptionsContextKey struct{}
|
||||
|
||||
// WithRedisOptions sets advanced options for redis.
|
||||
func WithRedisOptions(options rclient.UniversalOptions) cache.Option {
|
||||
return func(o *cache.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
|
||||
o.Context = context.WithValue(o.Context, redisOptionsContextKey{}, options)
|
||||
}
|
||||
}
|
||||
|
||||
func newUniversalClient(options cache.Options) rclient.UniversalClient {
|
||||
if options.Context == nil {
|
||||
options.Context = context.Background()
|
||||
}
|
||||
|
||||
opts, ok := options.Context.Value(redisOptionsContextKey{}).(rclient.UniversalOptions)
|
||||
if !ok {
|
||||
addr := "redis://127.0.0.1:6379"
|
||||
if len(options.Address) > 0 {
|
||||
addr = options.Address
|
||||
}
|
||||
|
||||
redisOptions, err := rclient.ParseURL(addr)
|
||||
if err != nil {
|
||||
redisOptions = &rclient.Options{Addr: addr}
|
||||
}
|
||||
|
||||
return rclient.NewClient(redisOptions)
|
||||
}
|
||||
|
||||
if len(opts.Addrs) == 0 && len(options.Address) > 0 {
|
||||
opts.Addrs = []string{options.Address}
|
||||
}
|
||||
|
||||
return rclient.NewUniversalClient(&opts)
|
||||
}
|
||||
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
rclient "github.com/go-redis/redis/v8"
|
||||
"go-micro.dev/v6/cache"
|
||||
)
|
||||
|
||||
func Test_newUniversalClient(t *testing.T) {
|
||||
type fields struct {
|
||||
options cache.Options
|
||||
}
|
||||
type wantValues struct {
|
||||
username string
|
||||
password string
|
||||
address string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want wantValues
|
||||
}{
|
||||
{name: "No Url", fields: fields{options: cache.Options{}},
|
||||
want: wantValues{
|
||||
username: "",
|
||||
password: "",
|
||||
address: "127.0.0.1:6379",
|
||||
}},
|
||||
{name: "legacy Url", fields: fields{options: cache.Options{Address: "127.0.0.1:6379"}},
|
||||
want: wantValues{
|
||||
username: "",
|
||||
password: "",
|
||||
address: "127.0.0.1:6379",
|
||||
}},
|
||||
{name: "New Url", fields: fields{options: cache.Options{Address: "redis://127.0.0.1:6379"}},
|
||||
want: wantValues{
|
||||
username: "",
|
||||
password: "",
|
||||
address: "127.0.0.1:6379",
|
||||
}},
|
||||
{name: "Url with Pwd", fields: fields{options: cache.Options{Address: "redis://:password@redis:6379"}},
|
||||
want: wantValues{
|
||||
username: "",
|
||||
password: "password",
|
||||
address: "redis:6379",
|
||||
}},
|
||||
{name: "Url with username and Pwd", fields: fields{
|
||||
options: cache.Options{Address: "redis://username:password@redis:6379"}},
|
||||
want: wantValues{
|
||||
username: "username",
|
||||
password: "password",
|
||||
address: "redis:6379",
|
||||
}},
|
||||
|
||||
{name: "Sentinel Failover client", fields: fields{
|
||||
options: cache.Options{
|
||||
Context: context.WithValue(
|
||||
context.TODO(), redisOptionsContextKey{},
|
||||
rclient.UniversalOptions{MasterName: "master-name"}),
|
||||
}},
|
||||
want: wantValues{
|
||||
username: "",
|
||||
password: "",
|
||||
address: "FailoverClient", // <- Placeholder set by NewFailoverClient
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
univClient := newUniversalClient(tt.fields.options)
|
||||
client, ok := univClient.(*rclient.Client)
|
||||
if !ok {
|
||||
t.Errorf("newUniversalClient() expect a *redis.Client")
|
||||
return
|
||||
}
|
||||
if client.Options().Addr != tt.want.address {
|
||||
t.Errorf("newUniversalClient() Address = %v, want address %v", client.Options().Addr, tt.want.address)
|
||||
}
|
||||
if client.Options().Password != tt.want.password {
|
||||
t.Errorf("newUniversalClient() password = %v, want password %v", client.Options().Password, tt.want.password)
|
||||
}
|
||||
if client.Options().Username != tt.want.username {
|
||||
t.Errorf("newUniversalClient() username = %v, want username %v", client.Options().Username, tt.want.username)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_newUniversalClientCluster(t *testing.T) {
|
||||
type fields struct {
|
||||
options cache.Options
|
||||
}
|
||||
type wantValues struct {
|
||||
username string
|
||||
password string
|
||||
addrs []string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want wantValues
|
||||
}{
|
||||
{name: "Addrs in redis options", fields: fields{
|
||||
options: cache.Options{
|
||||
Address: "127.0.0.1:6379", // <- ignored
|
||||
Context: context.WithValue(
|
||||
context.TODO(), redisOptionsContextKey{},
|
||||
rclient.UniversalOptions{Addrs: []string{"127.0.0.1:6381", "127.0.0.1:6382"}}),
|
||||
}},
|
||||
want: wantValues{
|
||||
username: "",
|
||||
password: "",
|
||||
addrs: []string{"127.0.0.1:6381", "127.0.0.1:6382"},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
univClient := newUniversalClient(tt.fields.options)
|
||||
client, ok := univClient.(*rclient.ClusterClient)
|
||||
if !ok {
|
||||
t.Errorf("newUniversalClient() expect a *redis.ClusterClient")
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(client.Options().Addrs, tt.want.addrs) {
|
||||
t.Errorf("newUniversalClient() Addrs = %v, want addrs %v", client.Options().Addrs, tt.want.addrs)
|
||||
}
|
||||
if client.Options().Password != tt.want.password {
|
||||
t.Errorf("newUniversalClient() password = %v, want password %v", client.Options().Password, tt.want.password)
|
||||
}
|
||||
if client.Options().Username != tt.want.username {
|
||||
t.Errorf("newUniversalClient() username = %v, want username %v", client.Options().Username, tt.want.username)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
rclient "github.com/go-redis/redis/v8"
|
||||
"go-micro.dev/v6/cache"
|
||||
)
|
||||
|
||||
// NewRedisCache returns a new redis cache.
|
||||
func NewRedisCache(opts ...cache.Option) cache.Cache {
|
||||
options := cache.NewOptions(opts...)
|
||||
return &redisCache{
|
||||
opts: options,
|
||||
client: newUniversalClient(options),
|
||||
}
|
||||
}
|
||||
|
||||
type redisCache struct {
|
||||
opts cache.Options
|
||||
client rclient.UniversalClient
|
||||
}
|
||||
|
||||
func (c *redisCache) Get(ctx context.Context, key string) (interface{}, time.Time, error) {
|
||||
val, err := c.client.Get(ctx, key).Bytes()
|
||||
if err != nil && err == rclient.Nil {
|
||||
return nil, time.Time{}, cache.ErrKeyNotFound
|
||||
} else if err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
|
||||
dur, err := c.client.TTL(ctx, key).Result()
|
||||
if err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
if dur == -1 {
|
||||
return val, time.Unix(1<<63-1, 0), nil
|
||||
}
|
||||
if dur == -2 {
|
||||
return val, time.Time{}, cache.ErrItemExpired
|
||||
}
|
||||
|
||||
return val, time.Now().Add(dur), nil
|
||||
}
|
||||
|
||||
func (c *redisCache) Put(ctx context.Context, key string, val interface{}, dur time.Duration) error {
|
||||
return c.client.Set(ctx, key, val, dur).Err()
|
||||
}
|
||||
|
||||
func (c *redisCache) Delete(ctx context.Context, key string) error {
|
||||
return c.client.Del(ctx, key).Err()
|
||||
}
|
||||
|
||||
func (c *redisCache) String() string {
|
||||
return "redis"
|
||||
}
|
||||
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go-micro.dev/v6/cache"
|
||||
)
|
||||
|
||||
var (
|
||||
ctx = context.TODO()
|
||||
key string = "redistestkey"
|
||||
val interface{} = "hello go-micro"
|
||||
addr = cache.WithAddress("redis://127.0.0.1:6379")
|
||||
)
|
||||
|
||||
// TestMemCache tests the in-memory cache implementation.
|
||||
func TestCache(t *testing.T) {
|
||||
if len(os.Getenv("LOCAL")) == 0 {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
t.Run("CacheGetMiss", func(t *testing.T) {
|
||||
if _, _, err := NewRedisCache(addr).Get(ctx, key); err == nil {
|
||||
t.Error("expected to get no value from cache")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CacheGetHit", func(t *testing.T) {
|
||||
c := NewRedisCache(addr)
|
||||
|
||||
if err := c.Put(ctx, key, val, 0); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if a, _, err := c.Get(ctx, key); err != nil {
|
||||
t.Errorf("Expected a value, got err: %s", err)
|
||||
} else if string(a.([]byte)) != val {
|
||||
t.Errorf("Expected '%v', got '%v'", val, a)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CacheGetExpired", func(t *testing.T) {
|
||||
c := NewRedisCache(addr)
|
||||
d := 20 * time.Millisecond
|
||||
|
||||
if err := c.Put(ctx, key, val, d); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
<-time.After(25 * time.Millisecond)
|
||||
if _, _, err := c.Get(ctx, key); err == nil {
|
||||
t.Error("expected to get no value from cache")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CacheGetValid", func(t *testing.T) {
|
||||
c := NewRedisCache(addr)
|
||||
e := 25 * time.Millisecond
|
||||
|
||||
if err := c.Put(ctx, key, val, e); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
<-time.After(20 * time.Millisecond)
|
||||
if _, _, err := c.Get(ctx, key); err != nil {
|
||||
t.Errorf("expected a value, got err: %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CacheDeleteHit", func(t *testing.T) {
|
||||
c := NewRedisCache(addr)
|
||||
|
||||
if err := c.Put(ctx, key, val, 0); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := c.Delete(ctx, key); err != nil {
|
||||
t.Errorf("Expected to delete an item, got err: %s", err)
|
||||
}
|
||||
|
||||
if _, _, err := c.Get(ctx, key); err == nil {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user