open-vault/physical/cache.go
Becca Petrin 03cf302e9a Move to "github.com/hashicorp/go-hclog" (#4227)
* logbridge with hclog and identical output

* Initial search & replace

This compiles, but there is a fair amount of TODO
and commented out code, especially around the
plugin logclient/logserver code.

* strip logbridge

* fix majority of tests

* update logxi aliases

* WIP fixing tests

* more test fixes

* Update test to hclog

* Fix format

* Rename hclog -> log

* WIP making hclog and logxi love each other

* update logger_test.go

* clean up merged comments

* Replace RawLogger interface with a Logger

* Add some logger names

* Replace Trace with Debug

* update builtin logical logging patterns

* Fix build errors

* More log updates

* update log approach in command and builtin

* More log updates

* update helper, http, and logical directories

* Update loggers

* Log updates

* Update logging

* Update logging

* Update logging

* Update logging

* update logging in physical

* prefixing and lowercase

* Update logging

* Move phyisical logging name to server command

* Fix som tests

* address jims feedback so far

* incorporate brians feedback so far

* strip comments

* move vault.go to logging package

* update Debug to Trace

* Update go-plugin deps

* Update logging based on review comments

* Updates from review

* Unvendor logxi

* Remove null_logger.go
2018-04-02 17:46:59 -07:00

191 lines
4.2 KiB
Go

package physical
import (
"context"
"sync/atomic"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/golang-lru"
"github.com/hashicorp/vault/helper/locksutil"
)
const (
// DefaultCacheSize is used if no cache size is specified for NewCache
DefaultCacheSize = 128 * 1024
)
// Cache is used to wrap an underlying physical backend
// and provide an LRU cache layer on top. Most of the reads done by
// Vault are for policy objects so there is a large read reduction
// by using a simple write-through cache.
type Cache struct {
backend Backend
lru *lru.TwoQueueCache
locks []*locksutil.LockEntry
logger log.Logger
enabled *uint32
}
// TransactionalCache is a Cache that wraps the physical that is transactional
type TransactionalCache struct {
*Cache
Transactional
}
// Verify Cache satisfies the correct interfaces
var _ ToggleablePurgemonster = (*Cache)(nil)
var _ ToggleablePurgemonster = (*TransactionalCache)(nil)
var _ Backend = (*Cache)(nil)
var _ Transactional = (*TransactionalCache)(nil)
// NewCache returns a physical cache of the given size.
// If no size is provided, the default size is used.
func NewCache(b Backend, size int, logger log.Logger) *Cache {
if logger.IsDebug() {
logger.Debug("creating LRU cache", "size", size)
}
if size <= 0 {
size = DefaultCacheSize
}
cache, _ := lru.New2Q(size)
c := &Cache{
backend: b,
lru: cache,
locks: locksutil.CreateLocks(),
logger: logger,
// This fails safe.
enabled: new(uint32),
}
return c
}
func NewTransactionalCache(b Backend, size int, logger log.Logger) *TransactionalCache {
c := &TransactionalCache{
Cache: NewCache(b, size, logger),
Transactional: b.(Transactional),
}
return c
}
// SetEnabled is used to toggle whether the cache is on or off. It must be
// called with true to actually activate the cache after creation.
func (c *Cache) SetEnabled(enabled bool) {
if enabled {
atomic.StoreUint32(c.enabled, 1)
return
}
atomic.StoreUint32(c.enabled, 0)
}
// Purge is used to clear the cache
func (c *Cache) Purge(ctx context.Context) {
// Lock the world
for _, lock := range c.locks {
lock.Lock()
defer lock.Unlock()
}
c.lru.Purge()
}
func (c *Cache) Put(ctx context.Context, entry *Entry) error {
if atomic.LoadUint32(c.enabled) == 0 {
return c.backend.Put(ctx, entry)
}
lock := locksutil.LockForKey(c.locks, entry.Key)
lock.Lock()
defer lock.Unlock()
err := c.backend.Put(ctx, entry)
if err == nil {
c.lru.Add(entry.Key, entry)
}
return err
}
func (c *Cache) Get(ctx context.Context, key string) (*Entry, error) {
if atomic.LoadUint32(c.enabled) == 0 {
return c.backend.Get(ctx, key)
}
lock := locksutil.LockForKey(c.locks, key)
lock.RLock()
defer lock.RUnlock()
// Check the LRU first
if raw, ok := c.lru.Get(key); ok {
if raw == nil {
return nil, nil
}
return raw.(*Entry), nil
}
// Read from the underlying backend
ent, err := c.backend.Get(ctx, key)
if err != nil {
return nil, err
}
// Cache the result
if ent != nil {
c.lru.Add(key, ent)
}
return ent, nil
}
func (c *Cache) Delete(ctx context.Context, key string) error {
if atomic.LoadUint32(c.enabled) == 0 {
return c.backend.Delete(ctx, key)
}
lock := locksutil.LockForKey(c.locks, key)
lock.Lock()
defer lock.Unlock()
err := c.backend.Delete(ctx, key)
if err == nil {
c.lru.Remove(key)
}
return err
}
func (c *Cache) List(ctx context.Context, prefix string) ([]string, error) {
// Always pass-through as this would be difficult to cache. For the same
// reason we don't lock as we can't reasonably know which locks to readlock
// ahead of time.
return c.backend.List(ctx, prefix)
}
func (c *TransactionalCache) Transaction(ctx context.Context, txns []*TxnEntry) error {
// Collect keys that need to be locked
var keys []string
for _, curr := range txns {
keys = append(keys, curr.Entry.Key)
}
// Lock the keys
for _, l := range locksutil.LocksForKeys(c.locks, keys) {
l.Lock()
defer l.Unlock()
}
if err := c.Transactional.Transaction(ctx, txns); err != nil {
return err
}
if atomic.LoadUint32(c.enabled) == 1 {
for _, txn := range txns {
switch txn.Operation {
case PutOperation:
c.lru.Add(txn.Entry.Key, txn.Entry)
case DeleteOperation:
c.lru.Remove(txn.Entry.Key)
}
}
}
return nil
}