mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 21:19:28 -06:00
198 lines
5.7 KiB
Go
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)
|
|
}
|