grafana/pkg/infra/remotecache/remotecache.go

195 lines
5.5 KiB
Go
Raw Normal View History

2019-03-08 13:49:16 -06:00
package remotecache
2019-02-14 16:13:46 -06:00
import (
"bytes"
2019-03-05 08:34:51 -06:00
"context"
2019-02-14 16:13:46 -06:00
"encoding/gob"
"errors"
2019-02-23 09:12:37 -06:00
"time"
2019-02-14 16:13:46 -06:00
"github.com/go-kit/log"
"github.com/grafana/grafana/pkg/infra/db"
glog "github.com/grafana/grafana/pkg/infra/log"
2019-02-14 16:13:46 -06:00
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/setting"
2019-02-14 16:13:46 -06:00
)
var (
2019-03-14 02:57:38 -05:00
// ErrCacheItemNotFound is returned if cache does not exist
2019-02-14 16:13:46 -06:00
ErrCacheItemNotFound = errors.New("cache item not found")
2019-03-14 02:57:38 -05:00
// ErrInvalidCacheType is returned if the type is invalid
ErrInvalidCacheType = errors.New("invalid remote cache name")
2019-03-14 03:22:03 -05:00
defaultMaxCacheExpiration = time.Hour * 24
2019-02-14 16:13:46 -06:00
)
const (
ServiceName = "RemoteCache"
)
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, secretsService secrets.Service) (*RemoteCache, error) {
var codec codec
if cfg.RemoteCacheOptions.Encryption {
codec = &encryptionCodec{secretsService}
} else {
codec = &gobCodec{}
}
client, err := createClient(cfg.RemoteCacheOptions, sqlStore, codec)
if err != nil {
return nil, err
}
s := &RemoteCache{
SQLStore: sqlStore,
Cfg: cfg,
log: glog.New("cache.remote"),
client: client,
}
return s, nil
2019-02-14 16:13:46 -06:00
}
2019-03-05 08:34:51 -06:00
// CacheStorage allows the caller to set, get and delete items in the cache.
// Cached items are stored as byte arrays and marshalled using "encoding/gob"
// so any struct added to the cache needs to be registered with `remotecache.Register`
// ex `remotecache.Register(CacheableStruct{})`
2019-03-05 08:34:51 -06:00
type CacheStorage interface {
// Get reads object from Cache
Get(ctx context.Context, key string) (interface{}, error)
2019-03-05 08:34:51 -06:00
2019-03-14 03:22:03 -05:00
// Set sets an object into the cache. if `expire` is set to zero it will default to 24h
Set(ctx context.Context, key string, value interface{}, expire time.Duration) error
2019-03-05 08:34:51 -06:00
// Delete object from cache
Delete(ctx context.Context, key string) error
2019-03-05 08:34:51 -06:00
}
2019-03-08 13:49:16 -06:00
// RemoteCache allows Grafana to cache data outside its own process
type RemoteCache struct {
2019-03-05 08:34:51 -06:00
log log.Logger
2019-03-11 04:44:16 -05:00
client CacheStorage
SQLStore db.DB
Cfg *setting.Cfg
2019-03-05 08:34:51 -06:00
}
2019-03-14 03:22:03 -05:00
// Get reads object from Cache
func (ds *RemoteCache) Get(ctx context.Context, key string) (interface{}, error) {
return ds.client.Get(ctx, key)
2019-03-11 04:44:16 -05:00
}
2019-03-14 03:22:03 -05:00
// Set sets an object into the cache. if `expire` is set to zero it will default to 24h
func (ds *RemoteCache) Set(ctx context.Context, key string, value interface{}, expire time.Duration) error {
2019-03-14 03:22:03 -05:00
if expire == 0 {
expire = defaultMaxCacheExpiration
}
return ds.client.Set(ctx, key, value, expire)
2019-03-11 04:44:16 -05:00
}
2019-03-14 03:22:03 -05:00
// Delete object from cache
func (ds *RemoteCache) Delete(ctx context.Context, key string) error {
return ds.client.Delete(ctx, key)
2019-03-11 04:44:16 -05:00
}
// Run starts the backend processes for cache clients.
2019-03-08 13:49:16 -06:00
func (ds *RemoteCache) Run(ctx context.Context) error {
// create new interface if more clients need GC jobs
2019-03-11 04:44:16 -05:00
backgroundjob, ok := ds.client.(registry.BackgroundService)
2019-03-05 08:34:51 -06:00
if ok {
return backgroundjob.Run(ctx)
}
<-ctx.Done()
return ctx.Err()
}
func createClient(opts *setting.RemoteCacheOptions, sqlstore db.DB, codec codec) (cache CacheStorage, err error) {
switch opts.Name {
case redisCacheType:
cache, err = newRedisStorage(opts, codec)
case memcachedCacheType:
cache = newMemcachedStorage(opts, codec)
case databaseCacheType:
cache = newDatabaseCache(sqlstore, codec)
default:
return nil, ErrInvalidCacheType
2019-02-15 02:48:32 -06:00
}
if err != nil {
return cache, err
2019-03-14 02:57:38 -05:00
}
if opts.Prefix != "" {
cache = &prefixCacheStorage{cache: cache, prefix: opts.Prefix}
2019-02-15 02:48:32 -06:00
}
return cache, nil
2019-02-15 02:48:32 -06:00
}
2019-03-05 08:34:51 -06:00
// Register records a type, identified by a value for that type, under its
// internal type name. That name will identify the concrete type of a value
// sent or received as an interface variable. Only types that will be
// transferred as implementations of interface values need to be registered.
// Expecting to be used only during initialization, it panics if the mapping
// between types and names is not a bijection.
func Register(value interface{}) {
gob.Register(value)
2019-02-14 16:13:46 -06:00
}
type cachedItem struct {
Val interface{}
2019-02-14 16:13:46 -06:00
}
type codec interface {
Encode(context.Context, *cachedItem) ([]byte, error)
Decode(context.Context, []byte, *cachedItem) error
}
type gobCodec struct{}
func (c *gobCodec) Encode(_ context.Context, item *cachedItem) ([]byte, error) {
2019-02-14 16:13:46 -06:00
buf := bytes.NewBuffer(nil)
err := gob.NewEncoder(buf).Encode(item)
return buf.Bytes(), err
}
func (c *gobCodec) Decode(_ context.Context, data []byte, out *cachedItem) error {
2019-02-14 16:13:46 -06:00
buf := bytes.NewBuffer(data)
return gob.NewDecoder(buf).Decode(&out)
}
type encryptionCodec struct {
secretsService secrets.Service
}
func (c *encryptionCodec) Encode(ctx context.Context, item *cachedItem) ([]byte, error) {
buf := bytes.NewBuffer(nil)
err := gob.NewEncoder(buf).Encode(item)
if err != nil {
return nil, err
}
return c.secretsService.Encrypt(ctx, buf.Bytes(), secrets.WithoutScope())
}
func (c *encryptionCodec) Decode(ctx context.Context, data []byte, out *cachedItem) error {
decrypted, err := c.secretsService.Decrypt(ctx, data)
if err != nil {
return err
}
buf := bytes.NewBuffer(decrypted)
return gob.NewDecoder(buf).Decode(&out)
}
type prefixCacheStorage struct {
cache CacheStorage
prefix string
}
func (pcs *prefixCacheStorage) Get(ctx context.Context, key string) (interface{}, error) {
return pcs.cache.Get(ctx, pcs.prefix+key)
}
func (pcs *prefixCacheStorage) Set(ctx context.Context, key string, value interface{}, expire time.Duration) error {
return pcs.cache.Set(ctx, pcs.prefix+key, value, expire)
}
func (pcs *prefixCacheStorage) Delete(ctx context.Context, key string) error {
return pcs.cache.Delete(ctx, pcs.prefix+key)
}