205 строки
4.0 KiB
Go
205 строки
4.0 KiB
Go
// Copyright 2017 Microsoft. All rights reserved.
|
|
// MIT License
|
|
|
|
package store
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Azure/azure-container-networking/log"
|
|
)
|
|
|
|
const (
|
|
// Default file name for backing persistent store.
|
|
defaultFileName = "azure-container-networking.json"
|
|
|
|
// Extension added to the file name for lock.
|
|
lockExtension = ".lock"
|
|
|
|
// Maximum number of retries before failing a lock call.
|
|
lockMaxRetries = 200
|
|
|
|
// Delay between lock retries.
|
|
lockRetryDelay = 100 * time.Millisecond
|
|
)
|
|
|
|
// jsonFileStore is an implementation of KeyValueStore using a local JSON file.
|
|
type jsonFileStore struct {
|
|
fileName string
|
|
data map[string]*json.RawMessage
|
|
inSync bool
|
|
locked bool
|
|
sync.Mutex
|
|
}
|
|
|
|
// NewJsonFileStore creates a new jsonFileStore object, accessed as a KeyValueStore.
|
|
func NewJsonFileStore(fileName string) (KeyValueStore, error) {
|
|
if fileName == "" {
|
|
fileName = defaultFileName
|
|
}
|
|
|
|
kvs := &jsonFileStore{
|
|
fileName: fileName,
|
|
data: make(map[string]*json.RawMessage),
|
|
}
|
|
|
|
return kvs, nil
|
|
}
|
|
|
|
// Read restores the value for the given key from persistent store.
|
|
func (kvs *jsonFileStore) Read(key string, value interface{}) error {
|
|
kvs.Mutex.Lock()
|
|
defer kvs.Mutex.Unlock()
|
|
|
|
// Read contents from file if memory is not in sync.
|
|
if !kvs.inSync {
|
|
// Open and parse the file if it exists.
|
|
file, err := os.Open(kvs.fileName)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return ErrKeyNotFound
|
|
}
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
// Decode to raw JSON messages.
|
|
if err := json.NewDecoder(file).Decode(&kvs.data); err != nil {
|
|
return err
|
|
}
|
|
|
|
kvs.inSync = true
|
|
}
|
|
|
|
raw, ok := kvs.data[key]
|
|
if !ok {
|
|
return ErrKeyNotFound
|
|
}
|
|
|
|
return json.Unmarshal(*raw, value)
|
|
}
|
|
|
|
// Write saves the given key value pair to persistent store.
|
|
func (kvs *jsonFileStore) Write(key string, value interface{}) error {
|
|
kvs.Mutex.Lock()
|
|
defer kvs.Mutex.Unlock()
|
|
|
|
var raw json.RawMessage
|
|
raw, err := json.Marshal(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
kvs.data[key] = &raw
|
|
|
|
return kvs.flush()
|
|
}
|
|
|
|
// Flush commits in-memory state to persistent store.
|
|
func (kvs *jsonFileStore) Flush() error {
|
|
kvs.Mutex.Lock()
|
|
defer kvs.Mutex.Unlock()
|
|
|
|
return kvs.flush()
|
|
}
|
|
|
|
// Lock-free flush for internal callers.
|
|
func (kvs *jsonFileStore) flush() error {
|
|
file, err := os.Create(kvs.fileName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
buf, err := json.MarshalIndent(&kvs.data, "", "\t")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := file.Write(buf); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Lock locks the store for exclusive access.
|
|
func (kvs *jsonFileStore) Lock(block bool) error {
|
|
kvs.Mutex.Lock()
|
|
defer kvs.Mutex.Unlock()
|
|
|
|
if kvs.locked {
|
|
return ErrStoreLocked
|
|
}
|
|
|
|
var lockFile *os.File
|
|
var err error
|
|
lockName := kvs.fileName + lockExtension
|
|
lockPerm := os.FileMode(0664) + os.FileMode(os.ModeExclusive)
|
|
|
|
// Try to acquire the lock file.
|
|
for i := 0; ; i++ {
|
|
lockFile, err = os.OpenFile(lockName, os.O_CREATE|os.O_EXCL|os.O_RDWR, lockPerm)
|
|
if err == nil {
|
|
break
|
|
}
|
|
|
|
if !block {
|
|
return ErrNonBlockingLockIsAlreadyLocked
|
|
}
|
|
|
|
if i == lockMaxRetries {
|
|
return ErrTimeoutLockingStore
|
|
}
|
|
|
|
time.Sleep(lockRetryDelay)
|
|
}
|
|
defer lockFile.Close()
|
|
|
|
// Write the process ID for easy identification.
|
|
if _, err = lockFile.WriteString(strconv.Itoa(os.Getpid())); err != nil {
|
|
return err
|
|
}
|
|
|
|
kvs.locked = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// Unlock unlocks the store.
|
|
func (kvs *jsonFileStore) Unlock() error {
|
|
kvs.Mutex.Lock()
|
|
defer kvs.Mutex.Unlock()
|
|
|
|
if !kvs.locked {
|
|
return ErrStoreNotLocked
|
|
}
|
|
|
|
err := os.Remove(kvs.fileName + lockExtension)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
kvs.inSync = false
|
|
kvs.locked = false
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetModificationTime returns the modification time of the persistent store.
|
|
func (kvs *jsonFileStore) GetModificationTime() (time.Time, error) {
|
|
kvs.Mutex.Lock()
|
|
defer kvs.Mutex.Unlock()
|
|
|
|
info, err := os.Stat(kvs.fileName)
|
|
if err != nil {
|
|
log.Printf("os.stat() for file %v failed: %v", kvs.fileName, err)
|
|
return time.Time{}.UTC(), err
|
|
}
|
|
|
|
return info.ModTime().UTC(), nil
|
|
}
|