redis client

This commit is contained in:
Dennis Coyle 2018-04-18 11:34:15 -04:00 committed by JT Olds
parent 1a65a65dd6
commit e6e1dae7fc
4 changed files with 272 additions and 0 deletions

53
storage/redis/client.go Normal file
View File

@ -0,0 +1,53 @@
package redis
import (
"time"
"github.com/go-redis/redis"
)
// Client defines the interface for communicating with a Storj redis instance
type Client interface {
Get(key string) ([]byte, error)
Set(key string, value []byte, ttl time.Duration) error
Ping() error
}
// Client is the entrypoint into Redis
type redisClient struct {
DB *redis.Client
}
// NewRedisClient returns a configured Client instance, verifying a sucessful connection to redis
func NewRedisClient(address, password string, db int) (Client, error) {
c := &redisClient{
DB: redis.NewClient(&redis.Options{
Addr: address,
Password: password,
DB: db,
}),
}
// ping here to verify we are able to connect to the redis instacne with the initialized client.
if err := c.DB.Ping().Err(); err != nil {
return nil, err
}
return c, nil
}
// Get looks up the provided key from the redis cache returning either an error or the result.
func (c *redisClient) Get(key string) ([]byte, error) {
return c.DB.Get(key).Bytes()
}
// Set adds a value to the provided key in the Redis cache, returning an error on failure.
func (c *redisClient) Set(key string, value []byte, ttl time.Duration) error {
return c.DB.Set(key, value, ttl).Err()
}
// Ping returns an error if pinging the underlying redis server failed
func (c *redisClient) Ping() error {
return c.DB.Ping().Err()
}

View File

@ -0,0 +1,49 @@
package redis
import (
"errors"
"time"
)
type mockRedisClient struct {
data map[string][]byte
getCalled int
setCalled int
pingCalled int
}
var ErrMissingKey = errors.New("missing")
var ErrForced = errors.New("error forced by using 'error' key in mock")
func (m *mockRedisClient) Get(key string) ([]byte, error) {
m.getCalled++
if key == "error" {
return []byte{}, ErrForced
}
v, ok := m.data[key]
if !ok {
return []byte{}, ErrMissingKey
}
return v, nil
}
func (m *mockRedisClient) Set(key string, value []byte, ttl time.Duration) error {
m.setCalled++
m.data[key] = value
return nil
}
func (m *mockRedisClient) Ping() error {
m.pingCalled++
return nil
}
func newMockRedisClient(d map[string][]byte) *mockRedisClient {
return &mockRedisClient{
data: d,
getCalled: 0,
setCalled: 0,
pingCalled: 0,
}
}

57
storage/redis/overlay.go Normal file
View File

@ -0,0 +1,57 @@
package redis
import (
"time"
"github.com/gogo/protobuf/proto"
"storj.io/storj/protos/overlay"
)
const defaultNodeExpiration = 61 * time.Minute
// OverlayClient is used to store overlay data in Redis
type OverlayClient struct {
DB Client
}
// NewOverlayClient returns a pointer to a new OverlayClient instance with an initalized connection to Redis.
func NewOverlayClient(address, password string, db int) (*OverlayClient, error) {
rc, err := NewRedisClient(address, password, db)
if err != nil {
return nil, err
}
o := &OverlayClient{
DB: rc,
}
// ping here to verify we are able to connect to the redis instacne with the initialized client.
if err := o.DB.Ping(); err != nil {
return nil, err
}
return o, nil
}
// Get looks up the provided nodeID from the redis cache
func (o *OverlayClient) Get(key string) (*overlay.NodeAddress, error) {
d, err := o.DB.Get(key)
if err != nil {
return nil, err
}
na := &overlay.NodeAddress{}
return na, proto.Unmarshal(d, na)
}
// Set adds a nodeID to the redis cache with a binary representation of proto defined NodeAddress
func (o *OverlayClient) Set(nodeID string, value overlay.NodeAddress) error {
data, err := proto.Marshal(&value)
if err != nil {
return err
}
return o.DB.Set(nodeID, data, defaultNodeExpiration)
}

View File

@ -0,0 +1,113 @@
package redis
import (
"testing"
"github.com/coyle/storj/protos/overlay"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/assert"
)
func TestGet(t *testing.T) {
cases := []struct {
testID string
expectedTimesCalled int
key string
expectedResponse *overlay.NodeAddress
expectedError error
client *mockRedisClient
}{
{
testID: "valid Get",
expectedTimesCalled: 1,
key: "foo",
expectedResponse: &overlay.NodeAddress{Transport: overlay.NodeTransport_TCP, Address: "127.0.0.1:9999"},
expectedError: nil,
client: newMockRedisClient(map[string][]byte{"foo": func() []byte {
na := &overlay.NodeAddress{Transport: overlay.NodeTransport_TCP, Address: "127.0.0.1:9999"}
d, err := proto.Marshal(na)
assert.NoError(t, err)
return d
}()}),
},
{
testID: "error Get from redis",
expectedTimesCalled: 1,
key: "error",
expectedResponse: nil,
expectedError: ErrForced,
client: newMockRedisClient(map[string][]byte{"error": func() []byte {
na := &overlay.NodeAddress{Transport: overlay.NodeTransport_TCP, Address: "127.0.0.1:9999"}
d, err := proto.Marshal(na)
assert.NoError(t, err)
return d
}()}),
},
{
testID: "get missing key",
expectedTimesCalled: 1,
key: "bar",
expectedResponse: nil,
expectedError: ErrMissingKey,
client: newMockRedisClient(map[string][]byte{"foo": func() []byte {
na := &overlay.NodeAddress{Transport: overlay.NodeTransport_TCP, Address: "127.0.0.1:9999"}
d, err := proto.Marshal(na)
assert.NoError(t, err)
return d
}()}),
},
}
for _, c := range cases {
t.Run(c.testID, func(t *testing.T) {
oc := OverlayClient{DB: c.client}
assert.Equal(t, 0, c.client.getCalled)
resp, err := oc.Get(c.key)
assert.Equal(t, c.expectedError, err)
assert.Equal(t, c.expectedResponse, resp)
assert.Equal(t, c.expectedTimesCalled, c.client.getCalled)
})
}
}
func TestSet(t *testing.T) {
cases := []struct {
testID string
expectedTimesCalled int
key string
value overlay.NodeAddress
expectedError error
client *mockRedisClient
}{
{
testID: "valid Set",
expectedTimesCalled: 1,
key: "foo",
value: overlay.NodeAddress{Transport: overlay.NodeTransport_TCP, Address: "127.0.0.1:9999"},
expectedError: nil,
client: newMockRedisClient(map[string][]byte{}),
},
}
for _, c := range cases {
t.Run(c.testID, func(t *testing.T) {
oc := OverlayClient{DB: c.client}
assert.Equal(t, 0, c.client.setCalled)
err := oc.Set(c.key, c.value)
assert.Equal(t, c.expectedError, err)
assert.Equal(t, c.expectedTimesCalled, c.client.setCalled)
v := c.client.data[c.key]
na := &overlay.NodeAddress{}
assert.NoError(t, proto.Unmarshal(v, na))
assert.Equal(t, na, &c.value)
})
}
}