-
Notifications
You must be signed in to change notification settings - Fork 194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RedisCache] For RateLimiting #77
Changes from all commits
8453698
313ac82
54e1c83
760ed68
ce20a53
1023b66
74c2647
1fb6834
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package elasticcache | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/Layr-Labs/eigenda/common" | ||
"github.com/go-redis/redis/v8" | ||
) | ||
|
||
type RedisClientConfig struct { | ||
EndpointURL string | ||
Port string | ||
} | ||
|
||
type RedisClient struct { | ||
redisClient *redis.Client | ||
logger common.Logger // Ensure common.Logger is imported correctly | ||
} | ||
|
||
func NewClient(cfg RedisClientConfig, logger common.Logger) (*RedisClient, error) { | ||
redisClient := redis.NewClient(&redis.Options{ | ||
Addr: cfg.EndpointURL + ":" + cfg.Port, | ||
Password: "", // no password set | ||
DB: 0, // use default DB | ||
}) | ||
|
||
// Test the Redis connection | ||
_, err := redisClient.Ping(context.Background()).Result() | ||
if err != nil { | ||
return nil, err // Return the error instead of logging and exiting | ||
} | ||
logger.Info("Redis connection successful") | ||
|
||
return &RedisClient{redisClient: redisClient, logger: logger}, nil | ||
} | ||
|
||
// Get retrieves a value from Redis | ||
func (c *RedisClient) Get(ctx context.Context, key string) *redis.StringCmd { | ||
return c.redisClient.Get(ctx, key) | ||
} | ||
|
||
// Set sets a value in Redis | ||
func (c *RedisClient) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd { | ||
return c.redisClient.Set(ctx, key, value, expiration) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package elasticcache_test | ||
|
||
import ( | ||
"context" | ||
"log" | ||
"testing" | ||
"time" | ||
|
||
elasticCache "github.com/Layr-Labs/eigenda/common/aws/elasticcache" | ||
cmock "github.com/Layr-Labs/eigenda/common/mock" | ||
"github.com/ory/dockertest/v3" | ||
"github.com/ory/dockertest/v3/docker" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestRedisClient(t *testing.T) { | ||
// Start Docker pool | ||
pool, err := dockertest.NewPool("") | ||
if err != nil { | ||
t.Fatalf("Could not connect to Docker: %v", err) | ||
} | ||
|
||
// Start Redis container | ||
resource, err := pool.RunWithOptions(&dockertest.RunOptions{ | ||
Repository: "redis", | ||
Tag: "latest", | ||
PortBindings: map[docker.Port][]docker.PortBinding{ | ||
"6379/tcp": {{HostIP: "", HostPort: "6379"}}, | ||
}, | ||
}) | ||
if err != nil { | ||
t.Fatalf("Could not start Redis container: %v", err) | ||
} | ||
|
||
// Delay cleanup until after all tests have run | ||
t.Cleanup(func() { | ||
if err := pool.Purge(resource); err != nil { | ||
t.Fatalf("Could not purge Redis container: %v", err) | ||
} | ||
}) | ||
|
||
// Wait for Redis to be ready | ||
if err := pool.Retry(func() error { | ||
// Perform a health check... | ||
return nil // return nil if healthy | ||
}); err != nil { | ||
log.Fatalf("Could not connect to Redis: %v", err) | ||
} | ||
|
||
// Set up Redis client | ||
cfg := elasticCache.RedisClientConfig{ | ||
EndpointURL: "localhost", | ||
Port: "6379", | ||
} | ||
|
||
logger := &cmock.Logger{} | ||
client, err := elasticCache.NewClient(cfg, logger) | ||
if err != nil { | ||
t.Fatalf("Failed to create Redis client: %v", err) | ||
} | ||
|
||
// Test setting a value | ||
key := "testKey" | ||
value := "testValue" | ||
_, err = client.Set(context.Background(), key, value, 10*time.Second).Result() | ||
assert.NoError(t, err, "Set should not return an error") | ||
|
||
// Test getting the value | ||
stringCmd := client.Get(context.Background(), key) | ||
result, err := stringCmd.Result() | ||
assert.NoError(t, err, "Get should not return an error") | ||
assert.Equal(t, value, result, "Get should return the value that was set") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package store | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
|
||
"github.com/Layr-Labs/eigenda/common" | ||
commoncache "github.com/Layr-Labs/eigenda/common/aws/elasticcache" | ||
) | ||
|
||
type RedisStore[T any] struct { | ||
client *commoncache.RedisClient | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be an interface and not implementation |
||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add this interface static checker. |
||
func NewRedisStore[T any](client *commoncache.RedisClient) common.KVStore[T] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preferable to return pointer struct instead of interface by convention |
||
return &RedisStore[T]{client: client} | ||
} | ||
|
||
func (s *RedisStore[T]) GetItem(ctx context.Context, key string) (*T, error) { | ||
val, err := s.client.Get(ctx, key).Result() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var item T | ||
err = json.Unmarshal([]byte(val), &item) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &item, nil | ||
} | ||
|
||
func (s *RedisStore[T]) UpdateItem(ctx context.Context, key string, value *T) error { | ||
jsonData, err := json.Marshal(value) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return s.client.Set(ctx, key, jsonData, 0).Err() // 0 means no expiration | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if we want do add expiration? What do you think of adding it as params? |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package store_test | ||
|
||
import ( | ||
"context" | ||
"log" | ||
"testing" | ||
"time" | ||
|
||
"github.com/Layr-Labs/eigenda/common" | ||
"github.com/Layr-Labs/eigenda/common/aws/elasticcache" | ||
cmock "github.com/Layr-Labs/eigenda/common/mock" | ||
"github.com/Layr-Labs/eigenda/common/store" | ||
"github.com/ory/dockertest/v3" | ||
"github.com/ory/dockertest/v3/docker" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestRedisStore(t *testing.T) { | ||
// Start Docker pool | ||
pool, err := dockertest.NewPool("") | ||
if err != nil { | ||
t.Fatalf("Could not connect to Docker: %v", err) | ||
} | ||
|
||
// Start Redis container | ||
resource, err := pool.RunWithOptions(&dockertest.RunOptions{ | ||
Repository: "redis", | ||
Tag: "latest", | ||
PortBindings: map[docker.Port][]docker.PortBinding{ | ||
"6379/tcp": {{HostIP: "", HostPort: "6379"}}, | ||
}, | ||
}) | ||
if err != nil { | ||
t.Fatalf("Could not start Redis container: %v", err) | ||
} | ||
|
||
// Delay cleanup until after all tests have run | ||
t.Cleanup(func() { | ||
if err := pool.Purge(resource); err != nil { | ||
t.Fatalf("Could not purge Redis container: %v", err) | ||
} | ||
}) | ||
|
||
// Wait for Redis to be ready | ||
if err := pool.Retry(func() error { | ||
// Perform a health check... | ||
return nil // return nil if healthy | ||
}); err != nil { | ||
log.Fatalf("Could not connect to Redis: %v", err) | ||
} | ||
|
||
// Set up the Redis client to point to your local Redis server | ||
clientConfig := elasticcache.RedisClientConfig{ | ||
EndpointURL: "localhost", | ||
Port: "6379", | ||
} | ||
|
||
redisClient, err := elasticcache.NewClient(clientConfig, &cmock.Logger{}) // Assuming logger can be nil | ||
if err != nil { | ||
t.Fatalf("Failed to create Redis client: %v", err) | ||
} | ||
|
||
redisStore := store.NewRedisStore[common.RateBucketParams](redisClient) | ||
|
||
// Test Update and Get Item | ||
ctx := context.Background() | ||
testKey := "testKey" | ||
testValue := common.RateBucketParams{ | ||
BucketLevels: []time.Duration{time.Second, time.Minute}, | ||
LastRequestTime: time.Now().UTC(), | ||
} | ||
|
||
err = redisStore.UpdateItem(ctx, testKey, &testValue) | ||
assert.NoError(t, err, "UpdateItem should not return an error") | ||
|
||
result, err := redisStore.GetItem(ctx, testKey) | ||
assert.NoError(t, err, "GetItem should not return an error") | ||
assert.Equal(t, testValue, *result, "GetItem should return the value that was set") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why v8 and not v9
https://github.com/redis/go-redis/releases/tag/v9.3.0