grafana/pkg/infra/remotecache/remotecache.go
2023-08-30 18:46:47 +03:00

198 lines
5.7 KiB
Go

package remotecache
import (
"context"
"errors"
"time"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/setting"
)
var (
// ErrCacheItemNotFound is returned if cache does not exist
ErrCacheItemNotFound = errors.New("cache item not found")
// ErrInvalidCacheType is returned if the type is invalid
ErrInvalidCacheType = errors.New("invalid remote cache name")
defaultMaxCacheExpiration = time.Hour * 24
)
const (
ServiceName = "RemoteCache"
)
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, usageStats usagestats.Service,
secretsService secrets.Service) (*RemoteCache, error) {
client, err := createClient(cfg.RemoteCacheOptions, sqlStore, secretsService)
if err != nil {
return nil, err
}
s := &RemoteCache{
SQLStore: sqlStore,
Cfg: cfg,
client: client,
}
usageStats.RegisterMetricsFunc(s.getUsageStats)
return s, nil
}
func (ds *RemoteCache) getUsageStats(ctx context.Context) (map[string]any, error) {
stats := map[string]any{}
stats["stats.remote_cache."+ds.Cfg.RemoteCacheOptions.Name+".count"] = 1
encryptVal := 0
if ds.Cfg.RemoteCacheOptions.Encryption {
encryptVal = 1
}
stats["stats.remote_cache.encrypt_enabled.count"] = encryptVal
return stats, nil
}
// 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{})`
type CacheStorage interface {
// Get gets the cache value as an byte array
Get(ctx context.Context, key string) ([]byte, error)
// Set saves the value as an byte array. if `expire` is set to zero it will default to 24h
Set(ctx context.Context, key string, value []byte, expire time.Duration) error
// Delete object from cache
Delete(ctx context.Context, key string) error
// Count returns the number of items in the cache.
// Optionaly a prefix can be provided to only count items with that prefix
// DO NOT USE. Not available for memcached.
Count(ctx context.Context, prefix string) (int64, error)
}
// RemoteCache allows Grafana to cache data outside its own process
type RemoteCache struct {
client CacheStorage
SQLStore db.DB
Cfg *setting.Cfg
}
// Get returns the cached value as an byte array
func (ds *RemoteCache) Get(ctx context.Context, key string) ([]byte, error) {
return ds.client.Get(ctx, key)
}
// Set stored the byte array in the cache
func (ds *RemoteCache) Set(ctx context.Context, key string, value []byte, expire time.Duration) error {
if expire == 0 {
expire = defaultMaxCacheExpiration
}
return ds.client.Set(ctx, key, value, expire)
}
// Delete object from cache
func (ds *RemoteCache) Delete(ctx context.Context, key string) error {
return ds.client.Delete(ctx, key)
}
// Count returns the number of items in the cache.
func (ds *RemoteCache) Count(ctx context.Context, prefix string) (int64, error) {
return ds.client.Count(ctx, prefix)
}
// Run starts the backend processes for cache clients.
func (ds *RemoteCache) Run(ctx context.Context) error {
// create new interface if more clients need GC jobs
backgroundjob, ok := ds.client.(registry.BackgroundService)
if ok {
return backgroundjob.Run(ctx)
}
<-ctx.Done()
return ctx.Err()
}
func createClient(opts *setting.RemoteCacheOptions, sqlstore db.DB, secretsService secrets.Service) (cache CacheStorage, err error) {
switch opts.Name {
case redisCacheType:
cache, err = newRedisStorage(opts)
case memcachedCacheType:
cache = newMemcachedStorage(opts)
case databaseCacheType:
cache = newDatabaseCache(sqlstore)
default:
return nil, ErrInvalidCacheType
}
if err != nil {
return cache, err
}
if opts.Prefix != "" {
cache = &prefixCacheStorage{cache: cache, prefix: opts.Prefix}
}
if opts.Encryption {
cache = &encryptedCacheStorage{cache: cache, secretsService: secretsService}
}
return cache, nil
}
type encryptedCacheStorage struct {
cache CacheStorage
secretsService encryptionService
}
type encryptionService interface {
Encrypt(ctx context.Context, payload []byte, opt secrets.EncryptionOptions) ([]byte, error)
Decrypt(ctx context.Context, payload []byte) ([]byte, error)
}
func (pcs *encryptedCacheStorage) Get(ctx context.Context, key string) ([]byte, error) {
data, err := pcs.cache.Get(ctx, key)
if err != nil {
return nil, err
}
return pcs.secretsService.Decrypt(ctx, data)
}
func (pcs *encryptedCacheStorage) Set(ctx context.Context, key string, value []byte, expire time.Duration) error {
encrypted, err := pcs.secretsService.Encrypt(ctx, value, secrets.WithoutScope())
if err != nil {
return err
}
return pcs.cache.Set(ctx, key, encrypted, expire)
}
func (pcs *encryptedCacheStorage) Delete(ctx context.Context, key string) error {
return pcs.cache.Delete(ctx, key)
}
func (pcs *encryptedCacheStorage) Count(ctx context.Context, prefix string) (int64, error) {
return pcs.cache.Count(ctx, prefix)
}
type prefixCacheStorage struct {
cache CacheStorage
prefix string
}
func (pcs *prefixCacheStorage) Get(ctx context.Context, key string) ([]byte, error) {
return pcs.cache.Get(ctx, pcs.prefix+key)
}
func (pcs *prefixCacheStorage) Set(ctx context.Context, key string, value []byte, 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)
}
func (pcs *prefixCacheStorage) Count(ctx context.Context, prefix string) (int64, error) {
return pcs.cache.Count(ctx, pcs.prefix+prefix)
}