244 lines
5.5 KiB
Go
244 lines
5.5 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package cachememdb
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync/atomic"
|
|
|
|
memdb "github.com/hashicorp/go-memdb"
|
|
)
|
|
|
|
const (
|
|
tableNameIndexer = "indexer"
|
|
)
|
|
|
|
// CacheMemDB is the underlying cache database for storing indexes.
|
|
type CacheMemDB struct {
|
|
db *atomic.Value
|
|
}
|
|
|
|
// New creates a new instance of CacheMemDB.
|
|
func New() (*CacheMemDB, error) {
|
|
db, err := newDB()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c := &CacheMemDB{
|
|
db: new(atomic.Value),
|
|
}
|
|
c.db.Store(db)
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func newDB() (*memdb.MemDB, error) {
|
|
cacheSchema := &memdb.DBSchema{
|
|
Tables: map[string]*memdb.TableSchema{
|
|
tableNameIndexer: {
|
|
Name: tableNameIndexer,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
// This index enables fetching the cached item based on the
|
|
// identifier of the index.
|
|
IndexNameID: {
|
|
Name: IndexNameID,
|
|
Unique: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
// This index enables fetching all the entries in cache for
|
|
// a given request path, in a given namespace.
|
|
IndexNameRequestPath: {
|
|
Name: IndexNameRequestPath,
|
|
Unique: false,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "RequestPath",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// This index enables fetching all the entries in cache
|
|
// belonging to the leases of a given token.
|
|
IndexNameLeaseToken: {
|
|
Name: IndexNameLeaseToken,
|
|
Unique: false,
|
|
AllowMissing: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "LeaseToken",
|
|
},
|
|
},
|
|
// This index enables fetching all the entries in cache
|
|
// that are tied to the given token, regardless of the
|
|
// entries belonging to the token or belonging to the
|
|
// lease.
|
|
IndexNameToken: {
|
|
Name: IndexNameToken,
|
|
Unique: true,
|
|
AllowMissing: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Token",
|
|
},
|
|
},
|
|
// This index enables fetching all the entries in cache for
|
|
// the given parent token.
|
|
IndexNameTokenParent: {
|
|
Name: IndexNameTokenParent,
|
|
Unique: false,
|
|
AllowMissing: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "TokenParent",
|
|
},
|
|
},
|
|
// This index enables fetching all the entries in cache for
|
|
// the given accessor.
|
|
IndexNameTokenAccessor: {
|
|
Name: IndexNameTokenAccessor,
|
|
Unique: true,
|
|
AllowMissing: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "TokenAccessor",
|
|
},
|
|
},
|
|
// This index enables fetching all the entries in cache for
|
|
// the given lease identifier.
|
|
IndexNameLease: {
|
|
Name: IndexNameLease,
|
|
Unique: true,
|
|
AllowMissing: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Lease",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
db, err := memdb.NewMemDB(cacheSchema)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return db, nil
|
|
}
|
|
|
|
// Get returns the index based on the indexer and the index values provided.
|
|
func (c *CacheMemDB) Get(indexName string, indexValues ...interface{}) (*Index, error) {
|
|
if !validIndexName(indexName) {
|
|
return nil, fmt.Errorf("invalid index name %q", indexName)
|
|
}
|
|
|
|
txn := c.db.Load().(*memdb.MemDB).Txn(false)
|
|
|
|
raw, err := txn.First(tableNameIndexer, indexName, indexValues...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if raw == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
index, ok := raw.(*Index)
|
|
if !ok {
|
|
return nil, errors.New("unable to parse index value from the cache")
|
|
}
|
|
|
|
return index, nil
|
|
}
|
|
|
|
// Set stores the index into the cache.
|
|
func (c *CacheMemDB) Set(index *Index) error {
|
|
if index == nil {
|
|
return errors.New("nil index provided")
|
|
}
|
|
|
|
txn := c.db.Load().(*memdb.MemDB).Txn(true)
|
|
defer txn.Abort()
|
|
|
|
if err := txn.Insert(tableNameIndexer, index); err != nil {
|
|
return fmt.Errorf("unable to insert index into cache: %v", err)
|
|
}
|
|
|
|
txn.Commit()
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetByPrefix returns all the cached indexes based on the index name and the
|
|
// value prefix.
|
|
func (c *CacheMemDB) GetByPrefix(indexName string, indexValues ...interface{}) ([]*Index, error) {
|
|
if !validIndexName(indexName) {
|
|
return nil, fmt.Errorf("invalid index name %q", indexName)
|
|
}
|
|
|
|
indexName = indexName + "_prefix"
|
|
|
|
// Get all the objects
|
|
txn := c.db.Load().(*memdb.MemDB).Txn(false)
|
|
|
|
iter, err := txn.Get(tableNameIndexer, indexName, indexValues...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var indexes []*Index
|
|
for {
|
|
obj := iter.Next()
|
|
if obj == nil {
|
|
break
|
|
}
|
|
index, ok := obj.(*Index)
|
|
if !ok {
|
|
return nil, fmt.Errorf("failed to cast cached index")
|
|
}
|
|
|
|
indexes = append(indexes, index)
|
|
}
|
|
|
|
return indexes, nil
|
|
}
|
|
|
|
// Evict removes an index from the cache based on index name and value.
|
|
func (c *CacheMemDB) Evict(indexName string, indexValues ...interface{}) error {
|
|
index, err := c.Get(indexName, indexValues...)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to fetch index on cache deletion: %v", err)
|
|
}
|
|
|
|
if index == nil {
|
|
return nil
|
|
}
|
|
|
|
txn := c.db.Load().(*memdb.MemDB).Txn(true)
|
|
defer txn.Abort()
|
|
|
|
if err := txn.Delete(tableNameIndexer, index); err != nil {
|
|
return fmt.Errorf("unable to delete index from cache: %v", err)
|
|
}
|
|
|
|
txn.Commit()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Flush resets the underlying cache object.
|
|
func (c *CacheMemDB) Flush() error {
|
|
newDB, err := newDB()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.db.Store(newDB)
|
|
|
|
return nil
|
|
}
|