go/storage/mockcache.go (282 lines of code) (raw):
package storage
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"path/filepath" // used for glob-like matching in Keys
"sort"
"strings"
"sync"
"time"
"github.com/golang/glog"
"github.com/mozilla/crlite/go"
)
type MockRemoteCache struct {
mu sync.Mutex
Data map[string][]string
Expirations map[string]time.Time
Duplicate int
CommitLock *string
Epoch uint64
}
func NewMockRemoteCache() *MockRemoteCache {
return &MockRemoteCache{
Data: make(map[string][]string),
Expirations: make(map[string]time.Time),
Duplicate: 0,
}
}
func (ec *MockRemoteCache) cleanupExpiry() {
// ec.mu must be held
now := time.Now()
for key, timestamp := range ec.Expirations {
if timestamp.Before(now) {
delete(ec.Data, key)
delete(ec.Expirations, key)
}
}
}
func (ec *MockRemoteCache) SetInsert(key string, entry string) (bool, error) {
ec.mu.Lock()
defer ec.mu.Unlock()
count := len(ec.Data[key])
idx := sort.Search(count, func(i int) bool {
return strings.Compare(entry, ec.Data[key][i]) <= 0
})
var cmp int
if idx < count {
cmp = strings.Compare(entry, ec.Data[key][idx])
}
if idx < count && cmp == 0 {
glog.V(3).Infof("[%s] Entry already known: %s (pos=%d)", key, entry, idx)
return false, nil
}
// Non-allocating insert, see https://github.com/golang/go/wiki/SliceTricks
glog.V(3).Infof("[%s] Entry unknown: %s (pos=%d)", key, entry, idx)
ec.Data[key] = append(ec.Data[key], "")
copy(ec.Data[key][idx+1:], ec.Data[key][idx:])
ec.Data[key][idx] = entry
return true, nil
}
func (ec *MockRemoteCache) setRemove(key string, entry string) error {
ec.mu.Lock()
defer ec.mu.Unlock()
count := len(ec.Data[key])
idx := sort.Search(count, func(i int) bool {
return strings.Compare(entry, ec.Data[key][i]) <= 0
})
var cmp int
if idx < count {
cmp = strings.Compare(entry, ec.Data[key][idx])
}
if idx < count && cmp == 0 {
if count == 1 {
delete(ec.Data, key)
} else {
ec.Data[key][idx] = ec.Data[key][count-1]
ec.Data[key] = ec.Data[key][:count-1]
}
return nil
}
return nil
}
func (ec *MockRemoteCache) SetRemove(key string, entries []string) error {
for _, entry := range entries {
err := ec.setRemove(key, entry)
if err != nil {
return err
}
}
ec.mu.Lock()
defer ec.mu.Unlock()
ec.cleanupExpiry()
return nil
}
func (ec *MockRemoteCache) SetContains(key string, entry string) (bool, error) {
ec.mu.Lock()
defer ec.mu.Unlock()
ec.cleanupExpiry()
count := len(ec.Data[key])
idx := sort.Search(count, func(i int) bool {
return strings.Compare(entry, ec.Data[key][i]) <= 0
})
var cmp int
if idx < count {
cmp = strings.Compare(entry, ec.Data[key][idx])
}
if idx < count && cmp == 0 {
return true, nil
}
return false, nil
}
func (ec *MockRemoteCache) SetList(key string) ([]string, error) {
ec.mu.Lock()
defer ec.mu.Unlock()
ec.cleanupExpiry()
return ec.Data[key], nil
}
func (ec *MockRemoteCache) SetToChan(key string, c chan<- string) error {
ec.mu.Lock()
defer ec.mu.Unlock()
defer close(c)
ec.cleanupExpiry()
for i := 0; i < ec.Duplicate+1; i++ {
for _, v := range ec.Data[key] {
c <- v
}
}
return nil
}
func (ec *MockRemoteCache) SetCardinality(key string) (int, error) {
ec.mu.Lock()
defer ec.mu.Unlock()
return len(ec.Data[key]), nil
}
func (ec *MockRemoteCache) Exists(key string) (bool, error) {
ec.mu.Lock()
defer ec.mu.Unlock()
ec.cleanupExpiry()
_, ok := ec.Data[key]
return ok, nil
}
func (ec *MockRemoteCache) ExpireAt(key string, expTime time.Time) error {
ec.mu.Lock()
defer ec.mu.Unlock()
ec.Expirations[key] = expTime
return nil
}
func (ec *MockRemoteCache) KeysToChan(pattern string, c chan<- string) error {
ec.mu.Lock()
defer ec.mu.Unlock()
defer close(c)
for key := range ec.Data {
matched, err := filepath.Match(pattern, key)
if err != nil {
return err
}
if matched {
c <- key
}
}
return nil
}
func (ec *MockRemoteCache) StoreLogState(log *types.CTLogState) error {
ec.mu.Lock()
defer ec.mu.Unlock()
encoded, err := json.Marshal(log)
if err != nil {
return err
}
ec.Data["log::"+log.ShortURL] = []string{string(encoded)}
return nil
}
func (ec *MockRemoteCache) LoadLogState(shortUrl string) (*types.CTLogState, error) {
ec.mu.Lock()
defer ec.mu.Unlock()
data, ok := ec.Data["log::"+shortUrl]
if !ok {
return nil, fmt.Errorf("Log state not found")
}
if len(data) != 1 {
return nil, fmt.Errorf("Unexpected number of log states")
}
var log types.CTLogState
if err := json.Unmarshal([]byte(data[0]), &log); err != nil {
return nil, err
}
return &log, nil
}
func (ec *MockRemoteCache) LoadAllLogStates() ([]types.CTLogState, error) {
ec.mu.Lock()
defer ec.mu.Unlock()
var logStates []types.CTLogState
for key, value := range ec.Data {
if strings.HasPrefix(key, "log::") {
var log types.CTLogState
if err := json.Unmarshal([]byte(value[0]), &log); err != nil {
return nil, err
}
logStates = append(logStates, log)
}
}
return logStates, nil
}
func (ec *MockRemoteCache) Migrate(logData *types.CTLogMetadata) error {
ec.mu.Lock()
defer ec.mu.Unlock()
return nil
}
func (ec *MockRemoteCache) AcquireCommitLock() (*string, error) {
ec.mu.Lock()
defer ec.mu.Unlock()
randomBytes := make([]byte, 16)
if _, err := rand.Read(randomBytes); err != nil {
return nil, err
}
commitLockToken := base64.URLEncoding.EncodeToString(randomBytes)
if ec.CommitLock == nil {
ec.CommitLock = &commitLockToken
return &commitLockToken, nil
}
return nil, nil
}
func (ec *MockRemoteCache) ReleaseCommitLock(aToken string) {
ec.mu.Lock()
defer ec.mu.Unlock()
hasLock := ec.CommitLock != nil && *ec.CommitLock == aToken
if hasLock {
ec.CommitLock = nil
}
}
func (ec *MockRemoteCache) HasCommitLock(aToken string) (bool, error) {
ec.mu.Lock()
defer ec.mu.Unlock()
return ec.CommitLock != nil && *ec.CommitLock == aToken, nil
}
func (ec *MockRemoteCache) GetEpoch() (uint64, error) {
ec.mu.Lock()
defer ec.mu.Unlock()
return ec.Epoch, nil
}
func (ec *MockRemoteCache) NextEpoch() error {
ec.mu.Lock()
defer ec.mu.Unlock()
ec.Epoch += 1
return nil
}
func (ec *MockRemoteCache) Restore(aEpoch uint64, aLogStates []types.CTLogState) error {
ec.mu.Lock()
ec.Epoch = aEpoch
ec.mu.Unlock()
for key, _ := range ec.Data {
if strings.HasPrefix(key, "log::") {
delete(ec.Data, key)
}
}
for _, logState := range aLogStates {
err := ec.StoreLogState(&logState)
if err != nil {
return err
}
}
return nil
}
func (ec *MockRemoteCache) AddPreIssuerAlias(aPreIssuer types.Issuer, aIssuer types.Issuer) error {
key := fmt.Sprintf("preissuer::%s", aPreIssuer.ID())
added, err := ec.SetInsert(key, aIssuer.ID())
if err == nil && added {
glog.Warningf("Added preissuer alias %s -> %s", aPreIssuer.ID(), aIssuer.ID())
}
return err
}
func (ec *MockRemoteCache) GetPreIssuerAliases(aPreIssuer types.Issuer) ([]types.Issuer, error) {
key := fmt.Sprintf("preissuer::%s", aPreIssuer.ID())
aliases, err := ec.SetList(key)
if err != nil {
return nil, err
}
issuerList := make([]types.Issuer, 0, len(aliases))
for _, alias := range aliases {
issuerList = append(issuerList, types.NewIssuerFromString(alias))
}
return issuerList, nil
}