redis client
This commit is contained in:
parent
1a65a65dd6
commit
e6e1dae7fc
53
storage/redis/client.go
Normal file
53
storage/redis/client.go
Normal 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()
|
||||
}
|
49
storage/redis/mock_client.go
Normal file
49
storage/redis/mock_client.go
Normal 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
57
storage/redis/overlay.go
Normal 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)
|
||||
}
|
113
storage/redis/overlay_test.go
Normal file
113
storage/redis/overlay_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user