clean up from yifan

This commit is contained in:
Xiang Li 2013-09-28 16:26:19 -07:00
parent a568c6dc75
commit da83ee223b
19 changed files with 443 additions and 586 deletions

View File

@ -9,7 +9,7 @@ import (
"time" "time"
etcdErr "github.com/coreos/etcd/error" etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/file_system" "github.com/coreos/etcd/store"
"github.com/coreos/go-raft" "github.com/coreos/go-raft"
) )
@ -39,7 +39,7 @@ func (c *CreateCommand) CommandName() string {
// Create node // Create node
func (c *CreateCommand) Apply(server *raft.Server) (interface{}, error) { func (c *CreateCommand) Apply(server *raft.Server) (interface{}, error) {
e, err := etcdFs.Create(c.Key, c.Value, c.ExpireTime, server.CommitIndex(), server.Term()) e, err := etcdStore.Create(c.Key, c.Value, c.ExpireTime, server.CommitIndex(), server.Term())
if err != nil { if err != nil {
debug(err) debug(err)
@ -63,7 +63,7 @@ func (c *UpdateCommand) CommandName() string {
// Update node // Update node
func (c *UpdateCommand) Apply(server *raft.Server) (interface{}, error) { func (c *UpdateCommand) Apply(server *raft.Server) (interface{}, error) {
e, err := etcdFs.Update(c.Key, c.Value, c.ExpireTime, server.CommitIndex(), server.Term()) e, err := etcdStore.Update(c.Key, c.Value, c.ExpireTime, server.CommitIndex(), server.Term())
if err != nil { if err != nil {
debug(err) debug(err)
@ -89,7 +89,7 @@ func (c *TestAndSetCommand) CommandName() string {
// Set the key-value pair if the current value of the key equals to the given prevValue // Set the key-value pair if the current value of the key equals to the given prevValue
func (c *TestAndSetCommand) Apply(server *raft.Server) (interface{}, error) { func (c *TestAndSetCommand) Apply(server *raft.Server) (interface{}, error) {
e, err := etcdFs.TestAndSet(c.Key, c.PrevValue, c.PrevIndex, e, err := etcdStore.TestAndSet(c.Key, c.PrevValue, c.PrevIndex,
c.Value, c.ExpireTime, server.CommitIndex(), server.Term()) c.Value, c.ExpireTime, server.CommitIndex(), server.Term())
if err != nil { if err != nil {
@ -114,7 +114,7 @@ func (c *GetCommand) CommandName() string {
// Get the value of key // Get the value of key
func (c *GetCommand) Apply(server *raft.Server) (interface{}, error) { func (c *GetCommand) Apply(server *raft.Server) (interface{}, error) {
e, err := etcdFs.Get(c.Key, c.Recursive, c.Sorted, server.CommitIndex(), server.Term()) e, err := etcdStore.Get(c.Key, c.Recursive, c.Sorted, server.CommitIndex(), server.Term())
if err != nil { if err != nil {
debug(err) debug(err)
@ -137,7 +137,7 @@ func (c *DeleteCommand) CommandName() string {
// Delete the key // Delete the key
func (c *DeleteCommand) Apply(server *raft.Server) (interface{}, error) { func (c *DeleteCommand) Apply(server *raft.Server) (interface{}, error) {
e, err := etcdFs.Delete(c.Key, c.Recursive, server.CommitIndex(), server.Term()) e, err := etcdStore.Delete(c.Key, c.Recursive, server.CommitIndex(), server.Term())
if err != nil { if err != nil {
debug(err) debug(err)
@ -160,7 +160,7 @@ func (c *WatchCommand) CommandName() string {
} }
func (c *WatchCommand) Apply(server *raft.Server) (interface{}, error) { func (c *WatchCommand) Apply(server *raft.Server) (interface{}, error) {
eventChan, err := etcdFs.Watch(c.Key, c.Recursive, c.SinceIndex, server.CommitIndex(), server.Term()) eventChan, err := etcdStore.Watch(c.Key, c.Recursive, c.SinceIndex, server.CommitIndex(), server.Term())
if err != nil { if err != nil {
return nil, err return nil, err
@ -197,7 +197,7 @@ func (c *JoinCommand) CommandName() string {
func (c *JoinCommand) Apply(raftServer *raft.Server) (interface{}, error) { func (c *JoinCommand) Apply(raftServer *raft.Server) (interface{}, error) {
// check if the join command is from a previous machine, who lost all its previous log. // check if the join command is from a previous machine, who lost all its previous log.
e, _ := etcdFs.Get(path.Join("/_etcd/machines", c.Name), false, false, raftServer.CommitIndex(), raftServer.Term()) e, _ := etcdStore.Get(path.Join("/_etcd/machines", c.Name), false, false, raftServer.CommitIndex(), raftServer.Term())
b := make([]byte, 8) b := make([]byte, 8)
binary.PutUvarint(b, raftServer.CommitIndex()) binary.PutUvarint(b, raftServer.CommitIndex())
@ -221,7 +221,7 @@ func (c *JoinCommand) Apply(raftServer *raft.Server) (interface{}, error) {
// add machine in etcd storage // add machine in etcd storage
key := path.Join("_etcd/machines", c.Name) key := path.Join("_etcd/machines", c.Name)
value := fmt.Sprintf("raft=%s&etcd=%s&raftVersion=%s", c.RaftURL, c.EtcdURL, c.RaftVersion) value := fmt.Sprintf("raft=%s&etcd=%s&raftVersion=%s", c.RaftURL, c.EtcdURL, c.RaftVersion)
etcdFs.Create(key, value, fileSystem.Permanent, raftServer.CommitIndex(), raftServer.Term()) etcdStore.Create(key, value, store.Permanent, raftServer.CommitIndex(), raftServer.Term())
if c.Name != r.Name() { // do not add self to the peer list if c.Name != r.Name() { // do not add self to the peer list
r.peersStats[c.Name] = &raftPeerStats{MinLatency: 1 << 63} r.peersStats[c.Name] = &raftPeerStats{MinLatency: 1 << 63}
@ -250,7 +250,7 @@ func (c *RemoveCommand) Apply(raftServer *raft.Server) (interface{}, error) {
// remove machine in etcd storage // remove machine in etcd storage
key := path.Join("_etcd/machines", c.Name) key := path.Join("_etcd/machines", c.Name)
_, err := etcdFs.Delete(key, false, raftServer.CommitIndex(), raftServer.Term()) _, err := etcdStore.Delete(key, false, raftServer.CommitIndex(), raftServer.Term())
delete(r.peersStats, c.Name) delete(r.peersStats, c.Name)
if err != nil { if err != nil {

View File

@ -32,27 +32,27 @@ func init() {
errors = make(map[int]string) errors = make(map[int]string)
// command related errors // command related errors
errors[100] = "Key Not Found" errors[EcodeKeyNotFound] = "Key Not Found"
errors[101] = "Test Failed" //test and set errors[EcodeTestFailed] = "Test Failed" //test and set
errors[102] = "Not A File" errors[EcodeNotFile] = "Not A File"
errors[103] = "Reached the max number of machines in the cluster" errors[EcodeNoMoreMachine] = "Reached the max number of machines in the cluster"
errors[104] = "Not A Directory" errors[EcodeNotDir] = "Not A Directory"
errors[105] = "Already exists" // create errors[EcodeNodeExist] = "Already exists" // create
errors[106] = "The prefix of given key is a keyword in etcd" errors[EcodeKeyIsPreserved] = "The prefix of given key is a keyword in etcd"
// Post form related errors // Post form related errors
errors[200] = "Value is Required in POST form" errors[EcodeValueRequired] = "Value is Required in POST form"
errors[201] = "PrevValue is Required in POST form" errors[EcodePrevValueRequired] = "PrevValue is Required in POST form"
errors[202] = "The given TTL in POST form is not a number" errors[EcodeTTLNaN] = "The given TTL in POST form is not a number"
errors[203] = "The given index in POST form is not a number" errors[EcodeIndexNaN] = "The given index in POST form is not a number"
// raft related errors // raft related errors
errors[300] = "Raft Internal Error" errors[EcodeRaftInternal] = "Raft Internal Error"
errors[301] = "During Leader Election" errors[EcodeLeaderElect] = "During Leader Election"
// etcd related errors // etcd related errors
errors[400] = "watcher is cleared due to etcd recovery" errors[EcodeWatcherCleared] = "watcher is cleared due to etcd recovery"
errors[401] = "The event in requested index is outdated and cleared" errors[EcodeEventIndexCleared] = "The event in requested index is outdated and cleared"
} }

View File

@ -10,7 +10,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/coreos/etcd/file_system" "github.com/coreos/etcd/store"
"github.com/coreos/go-raft" "github.com/coreos/go-raft"
) )
@ -136,7 +136,7 @@ type TLSConfig struct {
// //
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
var etcdFs *fileSystem.FileSystem var etcdStore *store.Store
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// //
@ -204,7 +204,7 @@ func main() {
info := getInfo(dirPath) info := getInfo(dirPath)
// Create etcd key-value store // Create etcd key-value store
etcdFs = fileSystem.New() etcdStore = store.New()
snapConf = newSnapshotConf() snapConf = newSnapshotConf()

View File

@ -7,7 +7,7 @@ import (
"strings" "strings"
etcdErr "github.com/coreos/etcd/error" etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/file_system" "github.com/coreos/etcd/store"
"github.com/coreos/go-raft" "github.com/coreos/go-raft"
) )
@ -120,7 +120,7 @@ func UpdateHttpHandler(w http.ResponseWriter, req *http.Request) error {
} }
// TODO: update should give at least one option // TODO: update should give at least one option
if value == "" && expireTime.Sub(fileSystem.Permanent) == 0 { if value == "" && expireTime.Sub(store.Permanent) == 0 {
return nil return nil
} }
@ -223,8 +223,7 @@ func VersionHttpHandler(w http.ResponseWriter, req *http.Request) error {
// Handler to return the basic stats of etcd // Handler to return the basic stats of etcd
func StatsHttpHandler(w http.ResponseWriter, req *http.Request) error { func StatsHttpHandler(w http.ResponseWriter, req *http.Request) error {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
//w.Write(etcdStore.Stats()) w.Write(etcdStore.JsonStats())
w.Write(etcdFs.Stats.GetStats())
w.Write(r.Stats()) w.Write(r.Stats())
return nil return nil
} }

View File

@ -1,146 +0,0 @@
package fileSystem
import (
"encoding/json"
"sync"
)
const (
// Operations that will be running serializely
StatsSetsHit = 100
StatsSetsMiss = 101
StatsDeletesHit = 102
StatsDeletesMiss = 103
StatsUpdatesHit = 104
StatsUpdatesMiss = 105
StatsTestAndSetsHit = 106
StatsTestAndSetsMiss = 107
StatsRecoveryHit = 108
StatsRecoveryMiss = 109
// concurrent operations
StatsGetsHit = 200
StatsGetsMiss = 201
StatsWatchHit = 300
StatsWatchMiss = 301
StatsInWatchingNum = 302
StatsSaveHit = 400
StatsSaveMiss = 401
)
type EtcdStats struct {
// Lock for synchronization
rwlock sync.RWMutex
// Number of get requests
GetsHit uint64 `json:"gets_hits"`
GetsMiss uint64 `json:"gets_misses"`
// Number of sets requests
SetsHit uint64 `json:"sets_hits"`
SetsMiss uint64 `json:"sets_misses"`
// Number of delete requests
DeletesHit uint64 `json:"deletes_hits"`
DeletesMiss uint64 `json:"deletes_misses"`
// Number of update requests
UpdatesHit uint64 `json:"updates_hits"`
UpdatesMiss uint64 `json:"updates_misses"`
// Number of testAndSet requests
TestAndSetsHit uint64 `json:"testAndSets_hits"`
TestAndSetsMiss uint64 `json:"testAndSets_misses"`
// Number of Watch requests
WatchHit uint64 `json:"watch_hit"`
WatchMiss uint64 `json:"watch_miss"`
InWatchingNum uint64 `json:"in_watching_number"`
// Number of save requests
SaveHit uint64 `json:"save_hit"`
SaveMiss uint64 `json:"save_miss"`
// Number of recovery requests
RecoveryHit uint64 `json:"recovery_hit"`
RecoveryMiss uint64 `json:"recovery_miss"`
}
func newStats() *EtcdStats {
e := new(EtcdStats)
return e
}
// Status() return the statistics info of etcd storage its recent start
func (e *EtcdStats) GetStats() []byte {
b, _ := json.Marshal(e)
return b
}
func (e *EtcdStats) TotalReads() uint64 {
return e.GetsHit + e.GetsMiss
}
func (e *EtcdStats) TotalWrites() uint64 {
return e.SetsHit + e.SetsMiss +
e.DeletesHit + e.DeletesMiss +
e.UpdatesHit + e.UpdatesMiss +
e.TestAndSetsHit + e.TestAndSetsMiss
}
func (e *EtcdStats) IncStats(field int) {
if field >= 200 {
e.rwlock.Lock()
switch field {
case StatsGetsHit:
e.GetsHit++
case StatsGetsMiss:
e.GetsMiss++
case StatsWatchHit:
e.WatchHit++
case StatsWatchMiss:
e.WatchMiss++
case StatsInWatchingNum:
e.InWatchingNum++
case StatsSaveHit:
e.SaveHit++
case StatsSaveMiss:
e.SaveMiss++
}
e.rwlock.Unlock()
} else {
e.rwlock.RLock()
switch field {
case StatsSetsHit:
e.SetsHit++
case StatsSetsMiss:
e.SetsMiss++
case StatsDeletesHit:
e.DeletesHit++
case StatsDeletesMiss:
e.DeletesMiss++
case StatsUpdatesHit:
e.UpdatesHit++
case StatsUpdatesMiss:
e.UpdatesMiss++
case StatsTestAndSetsHit:
e.TestAndSetsHit++
case StatsTestAndSetsMiss:
e.TestAndSetsMiss++
case StatsRecoveryHit:
e.RecoveryHit++
case StatsRecoveryMiss:
e.RecoveryMiss++
}
e.rwlock.RUnlock()
}
}

View File

@ -1,221 +0,0 @@
package fileSystem
import (
"math/rand"
"testing"
"time"
//"fmt"
)
func TestBasicStats(t *testing.T) {
fs := New()
keys := GenKeys(rand.Intn(100), 5)
i := uint64(0)
GetsHit := uint64(0)
GetsMiss := uint64(0)
SetsHit := uint64(0)
SetsMiss := uint64(0)
DeletesHit := uint64(0)
DeletesMiss := uint64(0)
UpdatesHit := uint64(0)
UpdatesMiss := uint64(0)
TestAndSetsHit := uint64(0)
TestAndSetsMiss := uint64(0)
WatchHit := uint64(0)
WatchMiss := uint64(0)
InWatchingNum := uint64(0)
SaveHit := uint64(0)
SaveMiss := uint64(0)
RecoveryHit := uint64(0)
RecoveryMiss := uint64(0)
for _, k := range keys {
i++
_, err := fs.Create(k, "bar", time.Now().Add(time.Second*time.Duration(rand.Intn(10))), i, 1)
if err != nil {
SetsMiss++
} else {
SetsHit++
}
}
for _, k := range keys {
_, err := fs.Get(k, false, false, i, 1)
if err != nil {
GetsMiss++
} else {
GetsHit++
}
}
for _, k := range keys {
i++
_, err := fs.Update(k, "foo", time.Now().Add(time.Second*time.Duration(rand.Intn(5))), i, 1)
if err != nil {
UpdatesMiss++
} else {
UpdatesHit++
}
}
for _, k := range keys {
_, err := fs.Get(k, false, false, i, 1)
if err != nil {
GetsMiss++
} else {
GetsHit++
}
}
for _, k := range keys {
i++
_, err := fs.TestAndSet(k, "foo", 0, "bar", Permanent, i, 1)
if err != nil {
TestAndSetsMiss++
} else {
TestAndSetsHit++
}
}
//fmt.Printf("#TestAndSet [%d]\n", TestAndSetsHit)
for _, k := range keys {
_, err := fs.Watch(k, false, 0, i, 1)
if err != nil {
WatchMiss++
} else {
WatchHit++
InWatchingNum++
}
}
//fmt.Printf("#Watch [%d]\n", WatchHit)
for _, k := range keys {
_, err := fs.Get(k, false, false, i, 1)
if err != nil {
GetsMiss++
} else {
GetsHit++
}
}
//fmt.Println("fs.index ", fs.Index)
for j := 0; j < 5; j++ {
b := make([]byte, 10)
err := fs.Recovery(b)
if err != nil {
RecoveryMiss++
}
b, err = fs.Save()
if err != nil {
SaveMiss++
} else {
SaveHit++
}
err = fs.Recovery(b)
if err != nil {
RecoveryMiss++
} else {
RecoveryHit++
}
}
//fmt.Println("fs.index after ", fs.Index)
//fmt.Println("stats.inwatching ", fs.Stats.InWatchingNum)
for _, k := range keys {
i++
_, err := fs.Delete(k, false, i, 1)
if err != nil {
DeletesMiss++
} else {
InWatchingNum--
DeletesHit++
}
}
//fmt.Printf("#Delete [%d] stats.deletehit [%d] \n", DeletesHit, fs.Stats.DeletesHit)
for _, k := range keys {
_, err := fs.Get(k, false, false, i, 1)
if err != nil {
GetsMiss++
} else {
GetsHit++
}
}
if GetsHit != fs.Stats.GetsHit {
t.Fatalf("GetsHit [%d] != Stats.GetsHit [%d]", GetsHit, fs.Stats.GetsHit)
}
if GetsMiss != fs.Stats.GetsMiss {
t.Fatalf("GetsMiss [%d] != Stats.GetsMiss [%d]", GetsMiss, fs.Stats.GetsMiss)
}
if SetsHit != fs.Stats.SetsHit {
t.Fatalf("SetsHit [%d] != Stats.SetsHit [%d]", SetsHit, fs.Stats.SetsHit)
}
if SetsMiss != fs.Stats.SetsMiss {
t.Fatalf("SetsMiss [%d] != Stats.SetsMiss [%d]", SetsMiss, fs.Stats.SetsMiss)
}
if DeletesHit != fs.Stats.DeletesHit {
t.Fatalf("DeletesHit [%d] != Stats.DeletesHit [%d]", DeletesHit, fs.Stats.DeletesHit)
}
if DeletesMiss != fs.Stats.DeletesMiss {
t.Fatalf("DeletesMiss [%d] != Stats.DeletesMiss [%d]", DeletesMiss, fs.Stats.DeletesMiss)
}
if UpdatesHit != fs.Stats.UpdatesHit {
t.Fatalf("UpdatesHit [%d] != Stats.UpdatesHit [%d]", UpdatesHit, fs.Stats.UpdatesHit)
}
if UpdatesMiss != fs.Stats.UpdatesMiss {
t.Fatalf("UpdatesMiss [%d] != Stats.UpdatesMiss [%d]", UpdatesMiss, fs.Stats.UpdatesMiss)
}
if TestAndSetsHit != fs.Stats.TestAndSetsHit {
t.Fatalf("TestAndSetsHit [%d] != Stats.TestAndSetsHit [%d]", TestAndSetsHit, fs.Stats.TestAndSetsHit)
}
if TestAndSetsMiss != fs.Stats.TestAndSetsMiss {
t.Fatalf("TestAndSetsMiss [%d] != Stats.TestAndSetsMiss [%d]", TestAndSetsMiss, fs.Stats.TestAndSetsMiss)
}
if SaveHit != fs.Stats.SaveHit {
t.Fatalf("SaveHit [%d] != Stats.SaveHit [%d]", SaveHit, fs.Stats.SaveHit)
}
if SaveMiss != fs.Stats.SaveMiss {
t.Fatalf("SaveMiss [%d] != Stats.SaveMiss [%d]", SaveMiss, fs.Stats.SaveMiss)
}
if WatchHit != fs.Stats.WatchHit {
t.Fatalf("WatchHit [%d] != Stats.WatchHit [%d]", WatchHit, fs.Stats.WatchHit)
}
if WatchMiss != fs.Stats.WatchMiss {
t.Fatalf("WatchMiss [%d] != Stats.WatchMiss [%d]", WatchMiss, fs.Stats.WatchMiss)
}
if InWatchingNum != fs.Stats.InWatchingNum {
t.Fatalf("InWatchingNum [%d] != Stats.InWatchingNum [%d]", InWatchingNum, fs.Stats.InWatchingNum)
}
if RecoveryHit != fs.Stats.RecoveryHit {
t.Fatalf("RecoveryHit [%d] != Stats.RecoveryHit [%d]", RecoveryHit, fs.Stats.RecoveryHit)
}
if RecoveryMiss != fs.Stats.RecoveryMiss {
t.Fatalf("RecoveryMiss [%d] != Stats.RecoveryMiss [%d]", RecoveryMiss, fs.Stats.RecoveryMiss)
}
//fmt.Println(GetsHit, GetsMiss, SetsHit, SetsMiss, DeletesHit, DeletesMiss, UpdatesHit, UpdatesMiss, TestAndSetsHit, TestAndSetsMiss, WatchHit, WatchMiss, InWatchingNum, SaveHit, SaveMiss, RecoveryHit, RecoveryMiss)
}

View File

@ -2,7 +2,7 @@ package main
// machineNum returns the number of machines in the cluster // machineNum returns the number of machines in the cluster
func machineNum() int { func machineNum() int {
e, err := etcdFs.Get("/_etcd/machines", false, false, r.CommitIndex(), r.Term()) e, err := etcdStore.Get("/_etcd/machines", false, false, r.CommitIndex(), r.Term())
if err != nil { if err != nil {
return 0 return 0

View File

@ -56,7 +56,7 @@ func readURL(nodeName string, urlName string) (string, bool) {
// convert nodeName to url from etcd storage // convert nodeName to url from etcd storage
key := path.Join("/_etcd/machines", nodeName) key := path.Join("/_etcd/machines", nodeName)
e, err := etcdFs.Get(key, false, false, r.CommitIndex(), r.Term()) e, err := etcdStore.Get(key, false, false, r.CommitIndex(), r.Term())
if err != nil { if err != nil {
return "", false return "", false

View File

@ -36,7 +36,7 @@ func newRaftServer(name string, url string, listenHost string, tlsConf *TLSConfi
raftTransporter := newTransporter(tlsConf.Scheme, tlsConf.Client, ElectionTimeout) raftTransporter := newTransporter(tlsConf.Scheme, tlsConf.Client, ElectionTimeout)
// Create raft server // Create raft server
server, err := raft.NewServer(name, dirPath, raftTransporter, etcdFs, nil) server, err := raft.NewServer(name, dirPath, raftTransporter, etcdStore, nil)
check(err) check(err)

View File

@ -1,4 +1,4 @@
package fileSystem package store
import ( import (
"fmt" "fmt"
@ -6,7 +6,7 @@ import (
"sync" "sync"
"time" "time"
etcdErr "github.com/xiangli-cmu/etcd/error" etcdErr "github.com/coreos/etcd/error"
) )
const ( const (

View File

@ -1,4 +1,4 @@
package fileSystem package store
import ( import (
"testing" "testing"

View File

@ -1,4 +1,4 @@
package fileSystem package store
import ( import (
"path" "path"
@ -6,7 +6,7 @@ import (
"sync" "sync"
"time" "time"
etcdErr "github.com/xiangli-cmu/etcd/error" etcdErr "github.com/coreos/etcd/error"
) )
var ( var (

91
store/stats.go Normal file
View File

@ -0,0 +1,91 @@
package store
import (
"encoding/json"
"sync/atomic"
)
const (
SetSuccess = 100
SetFail = 101
DeleteSuccess = 102
DeleteFail = 103
UpdateSuccess = 104
UpdateFail = 105
TestAndSetSuccess = 106
TestAndSetFail = 107
GetSuccess = 110
GetFail = 111
)
type Stats struct {
// Number of get requests
GetSuccess uint64 `json:"getsSuccess"`
GetFail uint64 `json:"getsFail"`
// Number of sets requests
SetSuccess uint64 `json:"setsSuccess"`
SetFail uint64 `json:"setsFail"`
// Number of delete requests
DeleteSuccess uint64 `json:"deleteSuccess"`
DeleteFail uint64 `json:"deleteFail"`
// Number of update requests
UpdateSuccess uint64 `json:"updateSuccess"`
UpdateFail uint64 `json:"updateFail"`
// Number of testAndSet requests
TestAndSetSuccess uint64 `json:"testAndSetSuccess"`
TestAndSetFail uint64 `json:"testAndSetFail"`
Watchers uint64 `json:"watchers"`
}
func newStats() *Stats {
s := new(Stats)
return s
}
// Status() return the statistics info of etcd storage its recent start
func (s *Stats) toJson() []byte {
b, _ := json.Marshal(s)
return b
}
func (s *Stats) TotalReads() uint64 {
return s.GetSuccess + s.GetFail
}
func (s *Stats) TotalWrites() uint64 {
return s.SetSuccess + s.SetFail +
s.DeleteSuccess + s.DeleteFail +
s.TestAndSetSuccess + s.TestAndSetFail +
s.UpdateSuccess + s.UpdateFail
}
func (s *Stats) Inc(field int) {
switch field {
case SetSuccess:
atomic.AddUint64(&s.SetSuccess, 1)
case SetFail:
atomic.AddUint64(&s.SetFail, 1)
case DeleteSuccess:
atomic.AddUint64(&s.DeleteSuccess, 1)
case DeleteFail:
atomic.AddUint64(&s.DeleteFail, 1)
case GetSuccess:
atomic.AddUint64(&s.GetSuccess, 1)
case GetFail:
atomic.AddUint64(&s.GetFail, 1)
case UpdateSuccess:
atomic.AddUint64(&s.UpdateSuccess, 1)
case UpdateFail:
atomic.AddUint64(&s.UpdateFail, 1)
case TestAndSetSuccess:
atomic.AddUint64(&s.TestAndSetSuccess, 1)
case TestAndSetFail:
atomic.AddUint64(&s.TestAndSetFail, 1)
}
}

139
store/stats_test.go Normal file
View File

@ -0,0 +1,139 @@
package store
import (
"math/rand"
"testing"
"time"
)
func TestBasicStats(t *testing.T) {
s := New()
keys := GenKeys(rand.Intn(100), 5)
var i uint64
var GetSuccess, GetFail, SetSuccess, SetFail, DeleteSuccess, DeleteFail uint64
var UpdateSuccess, UpdateFail, TestAndSetSuccess, TestAndSetFail, watcher_number uint64
for _, k := range keys {
i++
_, err := s.Create(k, "bar", time.Now().Add(time.Second*time.Duration(rand.Intn(10))), i, 1)
if err != nil {
SetFail++
} else {
SetSuccess++
}
}
for _, k := range keys {
_, err := s.Get(k, false, false, i, 1)
if err != nil {
GetFail++
} else {
GetSuccess++
}
}
for _, k := range keys {
i++
_, err := s.Update(k, "foo", time.Now().Add(time.Second*time.Duration(rand.Intn(5))), i, 1)
if err != nil {
UpdateFail++
} else {
UpdateSuccess++
}
}
for _, k := range keys {
_, err := s.Get(k, false, false, i, 1)
if err != nil {
GetFail++
} else {
GetSuccess++
}
}
for _, k := range keys {
i++
_, err := s.TestAndSet(k, "foo", 0, "bar", Permanent, i, 1)
if err != nil {
TestAndSetFail++
} else {
TestAndSetSuccess++
}
}
for _, k := range keys {
s.Watch(k, false, 0, i, 1)
watcher_number++
}
for _, k := range keys {
_, err := s.Get(k, false, false, i, 1)
if err != nil {
GetFail++
} else {
GetSuccess++
}
}
for _, k := range keys {
i++
_, err := s.Delete(k, false, i, 1)
if err != nil {
DeleteFail++
} else {
watcher_number--
DeleteSuccess++
}
}
for _, k := range keys {
_, err := s.Get(k, false, false, i, 1)
if err != nil {
GetFail++
} else {
GetSuccess++
}
}
if GetSuccess != s.Stats.GetSuccess {
t.Fatalf("GetSuccess [%d] != Stats.GetSuccess [%d]", GetSuccess, s.Stats.GetSuccess)
}
if GetFail != s.Stats.GetFail {
t.Fatalf("GetFail [%d] != Stats.GetFail [%d]", GetFail, s.Stats.GetFail)
}
if SetSuccess != s.Stats.SetSuccess {
t.Fatalf("SetSuccess [%d] != Stats.SetSuccess [%d]", SetSuccess, s.Stats.SetSuccess)
}
if SetFail != s.Stats.SetFail {
t.Fatalf("SetFail [%d] != Stats.SetFail [%d]", SetFail, s.Stats.SetFail)
}
if DeleteSuccess != s.Stats.DeleteSuccess {
t.Fatalf("DeleteSuccess [%d] != Stats.DeleteSuccess [%d]", DeleteSuccess, s.Stats.DeleteSuccess)
}
if DeleteFail != s.Stats.DeleteFail {
t.Fatalf("DeleteFail [%d] != Stats.DeleteFail [%d]", DeleteFail, s.Stats.DeleteFail)
}
if UpdateSuccess != s.Stats.UpdateSuccess {
t.Fatalf("UpdateSuccess [%d] != Stats.UpdateSuccess [%d]", UpdateSuccess, s.Stats.UpdateSuccess)
}
if UpdateFail != s.Stats.UpdateFail {
t.Fatalf("UpdateFail [%d] != Stats.UpdateFail [%d]", UpdateFail, s.Stats.UpdateFail)
}
if TestAndSetSuccess != s.Stats.TestAndSetSuccess {
t.Fatalf("TestAndSetSuccess [%d] != Stats.TestAndSetSuccess [%d]", TestAndSetSuccess, s.Stats.TestAndSetSuccess)
}
if TestAndSetFail != s.Stats.TestAndSetFail {
t.Fatalf("TestAndSetFail [%d] != Stats.TestAndSetFail [%d]", TestAndSetFail, s.Stats.TestAndSetFail)
}
}

View File

@ -1,4 +1,4 @@
package fileSystem package store
import ( import (
"encoding/json" "encoding/json"
@ -8,34 +8,34 @@ import (
"strings" "strings"
"time" "time"
etcdErr "github.com/xiangli-cmu/etcd/error" etcdErr "github.com/coreos/etcd/error"
) )
type FileSystem struct { type Store struct {
Root *Node Root *Node
WatcherHub *watcherHub WatcherHub *watcherHub
Index uint64 Index uint64
Term uint64 Term uint64
Stats *EtcdStats Stats *Stats
} }
func New() *FileSystem { func New() *Store {
fs := new(FileSystem) s := new(Store)
fs.Root = newDir("/", 0, 0, nil, "", Permanent) s.Root = newDir("/", 0, 0, nil, "", Permanent)
fs.Stats = newStats() s.Stats = newStats()
fs.WatcherHub = newWatchHub(1000, fs.Stats) s.WatcherHub = newWatchHub(1000)
return fs return s
} }
func (fs *FileSystem) Get(nodePath string, recursive, sorted bool, index uint64, term uint64) (*Event, error) { func (s *Store) Get(nodePath string, recursive, sorted bool, index uint64, term uint64) (*Event, error) {
nodePath = path.Clean(path.Join("/", nodePath)) nodePath = path.Clean(path.Join("/", nodePath))
n, err := fs.InternalGet(nodePath, index, term) n, err := s.InternalGet(nodePath, index, term)
if err != nil { if err != nil {
fs.Stats.IncStats(StatsGetsMiss) s.Stats.Inc(GetFail)
return nil, err return nil, err
} }
@ -82,61 +82,62 @@ func (fs *FileSystem) Get(nodePath string, recursive, sorted bool, index uint64,
e.TTL = int64(n.ExpireTime.Sub(time.Now())/time.Second) + 1 e.TTL = int64(n.ExpireTime.Sub(time.Now())/time.Second) + 1
} }
fs.Stats.IncStats(StatsGetsHit) s.Stats.Inc(GetSuccess)
return e, nil return e, nil
} }
// Create function creates the Node at nodePath. Create will help to create intermediate directories with no ttl. // Create function creates the Node at nodePath. Create will help to create intermediate directories with no ttl.
// If the node has already existed, create will fail. // If the node has already existed, create will fail.
// If any node on the path is a file, create will fail. // If any node on the path is a file, create will fail.
func (fs *FileSystem) Create(nodePath string, value string, expireTime time.Time, index uint64, term uint64) (*Event, error) { func (s *Store) Create(nodePath string, value string, expireTime time.Time, index uint64, term uint64) (*Event, error) {
nodePath = path.Clean(path.Join("/", nodePath)) nodePath = path.Clean(path.Join("/", nodePath))
// make sure we can create the node // make sure we can create the node
_, err := fs.InternalGet(nodePath, index, term) _, err := s.InternalGet(nodePath, index, term)
if err == nil { // key already exists if err == nil { // key already exists
fs.Stats.IncStats(StatsSetsMiss) s.Stats.Inc(SetFail)
return nil, etcdErr.NewError(etcdErr.EcodeNodeExist, nodePath) return nil, etcdErr.NewError(etcdErr.EcodeNodeExist, nodePath)
} }
etcdError, _ := err.(etcdErr.Error) etcdError, _ := err.(etcdErr.Error)
if etcdError.ErrorCode == 104 { // we cannot create the key due to meet a file while walking if etcdError.ErrorCode == 104 { // we cannot create the key due to meet a file while walking
fs.Stats.IncStats(StatsSetsMiss) s.Stats.Inc(SetFail)
return nil, err return nil, err
} }
dir, _ := path.Split(nodePath) dir, _ := path.Split(nodePath)
// walk through the nodePath, create dirs and get the last directory node // walk through the nodePath, create dirs and get the last directory node
d, err := fs.walk(dir, fs.checkDir) d, err := s.walk(dir, s.checkDir)
if err != nil { if err != nil {
fs.Stats.IncStats(StatsSetsMiss) s.Stats.Inc(SetFail)
return nil, err return nil, err
} }
e := newEvent(Create, nodePath, fs.Index, fs.Term) e := newEvent(Create, nodePath, s.Index, s.Term)
var n *Node var n *Node
if len(value) != 0 { // create file if len(value) != 0 { // create file
e.Value = value e.Value = value
n = newFile(nodePath, value, fs.Index, fs.Term, d, "", expireTime) n = newFile(nodePath, value, s.Index, s.Term, d, "", expireTime)
} else { // create directory } else { // create directory
e.Dir = true e.Dir = true
n = newDir(nodePath, fs.Index, fs.Term, d, "", expireTime) n = newDir(nodePath, s.Index, s.Term, d, "", expireTime)
} }
err = d.Add(n) err = d.Add(n)
if err != nil { if err != nil {
fs.Stats.IncStats(StatsSetsMiss) s.Stats.Inc(SetFail)
return nil, err return nil, err
} }
@ -147,28 +148,28 @@ func (fs *FileSystem) Create(nodePath string, value string, expireTime time.Time
e.TTL = int64(expireTime.Sub(time.Now())/time.Second) + 1 e.TTL = int64(expireTime.Sub(time.Now())/time.Second) + 1
} }
fs.WatcherHub.notify(e) s.WatcherHub.notify(e)
fs.Stats.IncStats(StatsSetsHit) s.Stats.Inc(SetSuccess)
return e, nil return e, nil
} }
// Update function updates the value/ttl of the node. // Update function updates the value/ttl of the node.
// If the node is a file, the value and the ttl can be updated. // If the node is a file, the value and the ttl can be updated.
// If the node is a directory, only the ttl can be updated. // If the node is a directory, only the ttl can be updated.
func (fs *FileSystem) Update(nodePath string, value string, expireTime time.Time, index uint64, term uint64) (*Event, error) { func (s *Store) Update(nodePath string, value string, expireTime time.Time, index uint64, term uint64) (*Event, error) {
n, err := fs.InternalGet(nodePath, index, term) n, err := s.InternalGet(nodePath, index, term)
if err != nil { // if the node does not exist, return error if err != nil { // if the node does not exist, return error
fs.Stats.IncStats(StatsUpdatesMiss) s.Stats.Inc(UpdateFail)
return nil, err return nil, err
} }
e := newEvent(Update, nodePath, fs.Index, fs.Term) e := newEvent(Update, nodePath, s.Index, s.Term)
if n.IsDir() { // if the node is a directory, we can only update ttl if n.IsDir() { // if the node is a directory, we can only update ttl
if len(value) != 0 { if len(value) != 0 {
fs.Stats.IncStats(StatsUpdatesMiss) s.Stats.Inc(UpdateFail)
return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath) return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath)
} }
@ -194,23 +195,23 @@ func (fs *FileSystem) Update(nodePath string, value string, expireTime time.Time
e.TTL = int64(expireTime.Sub(time.Now())/time.Second) + 1 e.TTL = int64(expireTime.Sub(time.Now())/time.Second) + 1
} }
fs.WatcherHub.notify(e) s.WatcherHub.notify(e)
fs.Stats.IncStats(StatsUpdatesHit) s.Stats.Inc(UpdateSuccess)
return e, nil return e, nil
} }
func (fs *FileSystem) TestAndSet(nodePath string, prevValue string, prevIndex uint64, func (s *Store) TestAndSet(nodePath string, prevValue string, prevIndex uint64,
value string, expireTime time.Time, index uint64, term uint64) (*Event, error) { value string, expireTime time.Time, index uint64, term uint64) (*Event, error) {
f, err := fs.InternalGet(nodePath, index, term) f, err := s.InternalGet(nodePath, index, term)
if err != nil { if err != nil {
fs.Stats.IncStats(StatsTestAndSetsMiss) s.Stats.Inc(TestAndSetFail)
return nil, err return nil, err
} }
if f.IsDir() { // can only test and set file if f.IsDir() { // can only test and set file
fs.Stats.IncStats(StatsTestAndSetsMiss) s.Stats.Inc(TestAndSetFail)
return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath) return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath)
} }
@ -221,23 +222,23 @@ func (fs *FileSystem) TestAndSet(nodePath string, prevValue string, prevIndex ui
e.Value = value e.Value = value
f.Write(value, index, term) f.Write(value, index, term)
fs.WatcherHub.notify(e) s.WatcherHub.notify(e)
fs.Stats.IncStats(StatsTestAndSetsHit) s.Stats.Inc(TestAndSetSuccess)
return e, nil return e, nil
} }
cause := fmt.Sprintf("[%v != %v] [%v != %v]", prevValue, f.Value, prevIndex, f.ModifiedIndex) cause := fmt.Sprintf("[%v != %v] [%v != %v]", prevValue, f.Value, prevIndex, f.ModifiedIndex)
fs.Stats.IncStats(StatsTestAndSetsMiss) s.Stats.Inc(TestAndSetFail)
return nil, etcdErr.NewError(etcdErr.EcodeTestFailed, cause) return nil, etcdErr.NewError(etcdErr.EcodeTestFailed, cause)
} }
// Delete function deletes the node at the given path. // Delete function deletes the node at the given path.
// If the node is a directory, recursive must be true to delete it. // If the node is a directory, recursive must be true to delete it.
func (fs *FileSystem) Delete(nodePath string, recursive bool, index uint64, term uint64) (*Event, error) { func (s *Store) Delete(nodePath string, recursive bool, index uint64, term uint64) (*Event, error) {
n, err := fs.InternalGet(nodePath, index, term) n, err := s.InternalGet(nodePath, index, term)
if err != nil { // if the node does not exist, return error if err != nil { // if the node does not exist, return error
fs.Stats.IncStats(StatsDeletesMiss) s.Stats.Inc(DeleteFail)
return nil, err return nil, err
} }
@ -250,37 +251,37 @@ func (fs *FileSystem) Delete(nodePath string, recursive bool, index uint64, term
} }
callback := func(path string) { // notify function callback := func(path string) { // notify function
fs.WatcherHub.notifyWithPath(e, path, true) s.WatcherHub.notifyWithPath(e, path, true)
} }
err = n.Remove(recursive, callback) err = n.Remove(recursive, callback)
if err != nil { if err != nil {
fs.Stats.IncStats(StatsDeletesMiss) s.Stats.Inc(DeleteFail)
return nil, err return nil, err
} }
fs.WatcherHub.notify(e) s.WatcherHub.notify(e)
fs.Stats.IncStats(StatsDeletesHit) s.Stats.Inc(DeleteSuccess)
return e, nil return e, nil
} }
func (fs *FileSystem) Watch(prefix string, recursive bool, sinceIndex uint64, index uint64, term uint64) (<-chan *Event, error) { func (s *Store) Watch(prefix string, recursive bool, sinceIndex uint64, index uint64, term uint64) (<-chan *Event, error) {
fs.Index, fs.Term = index, term s.Index, s.Term = index, term
if sinceIndex == 0 { if sinceIndex == 0 {
return fs.WatcherHub.watch(prefix, recursive, index+1) return s.WatcherHub.watch(prefix, recursive, index+1)
} }
return fs.WatcherHub.watch(prefix, recursive, sinceIndex) return s.WatcherHub.watch(prefix, recursive, sinceIndex)
} }
// walk function walks all the nodePath and apply the walkFunc on each directory // walk function walks all the nodePath and apply the walkFunc on each directory
func (fs *FileSystem) walk(nodePath string, walkFunc func(prev *Node, component string) (*Node, error)) (*Node, error) { func (s *Store) walk(nodePath string, walkFunc func(prev *Node, component string) (*Node, error)) (*Node, error) {
components := strings.Split(nodePath, "/") components := strings.Split(nodePath, "/")
curr := fs.Root curr := s.Root
var err error var err error
for i := 1; i < len(components); i++ { for i := 1; i < len(components); i++ {
@ -299,11 +300,11 @@ func (fs *FileSystem) walk(nodePath string, walkFunc func(prev *Node, component
} }
// InternalGet function get the node of the given nodePath. // InternalGet function get the node of the given nodePath.
func (fs *FileSystem) InternalGet(nodePath string, index uint64, term uint64) (*Node, error) { func (s *Store) InternalGet(nodePath string, index uint64, term uint64) (*Node, error) {
nodePath = path.Clean(path.Join("/", nodePath)) nodePath = path.Clean(path.Join("/", nodePath))
// update file system known index and term // update file system known index and term
fs.Index, fs.Term = index, term s.Index, s.Term = index, term
walkFunc := func(parent *Node, name string) (*Node, error) { walkFunc := func(parent *Node, name string) (*Node, error) {
@ -319,7 +320,7 @@ func (fs *FileSystem) InternalGet(nodePath string, index uint64, term uint64) (*
return nil, etcdErr.NewError(etcdErr.EcodeKeyNotFound, path.Join(parent.Path, name)) return nil, etcdErr.NewError(etcdErr.EcodeKeyNotFound, path.Join(parent.Path, name))
} }
f, err := fs.walk(nodePath, walkFunc) f, err := s.walk(nodePath, walkFunc)
if err != nil { if err != nil {
return nil, err return nil, err
@ -332,14 +333,14 @@ func (fs *FileSystem) InternalGet(nodePath string, index uint64, term uint64) (*
// If it is a directory, this function will return the pointer to that node. // If it is a directory, this function will return the pointer to that node.
// If it does not exist, this function will create a new directory and return the pointer to that node. // If it does not exist, this function will create a new directory and return the pointer to that node.
// If it is a file, this function will return error. // If it is a file, this function will return error.
func (fs *FileSystem) checkDir(parent *Node, dirName string) (*Node, error) { func (s *Store) checkDir(parent *Node, dirName string) (*Node, error) {
subDir, ok := parent.Children[dirName] subDir, ok := parent.Children[dirName]
if ok { if ok {
return subDir, nil return subDir, nil
} }
n := newDir(path.Join(parent.Path, dirName), fs.Index, fs.Term, parent, parent.ACL, Permanent) n := newDir(path.Join(parent.Path, dirName), s.Index, s.Term, parent, parent.ACL, Permanent)
parent.Children[dirName] = n parent.Children[dirName] = n
@ -350,24 +351,20 @@ func (fs *FileSystem) checkDir(parent *Node, dirName string) (*Node, error) {
// Save function will not be able to save the state of watchers. // Save function will not be able to save the state of watchers.
// Save function will not save the parent field of the node. Or there will // Save function will not save the parent field of the node. Or there will
// be cyclic dependencies issue for the json package. // be cyclic dependencies issue for the json package.
func (fs *FileSystem) Save() ([]byte, error) { func (s *Store) Save() ([]byte, error) {
clonedStore := New()
clonedStore.Root = s.Root.Clone()
clonedStore.WatcherHub = s.WatcherHub
clonedStore.Index = s.Index
clonedStore.Term = s.Term
clonedStore.Stats = s.Stats
fs.Stats.IncStats(StatsSaveHit) b, err := json.Marshal(clonedStore)
cloneFs := New()
cloneFs.Root = fs.Root.Clone()
b, err := json.Marshal(fs)
if err != nil { if err != nil {
fs.Stats.IncStats(StatsSaveMiss)
fs.Stats.rwlock.Lock()
fs.Stats.SaveHit-- // restore the savehit
fs.Stats.rwlock.Unlock()
return nil, err return nil, err
} }
fs.Stats.IncStats(StatsSaveHit)
return b, nil return b, nil
} }
@ -375,15 +372,18 @@ func (fs *FileSystem) Save() ([]byte, error) {
// It needs to recovery the parent field of the nodes. // It needs to recovery the parent field of the nodes.
// It needs to delete the expired nodes since the saved time and also // It needs to delete the expired nodes since the saved time and also
// need to create monitor go routines. // need to create monitor go routines.
func (fs *FileSystem) Recovery(state []byte) error { func (s *Store) Recovery(state []byte) error {
err := json.Unmarshal(state, fs) err := json.Unmarshal(state, s)
if err != nil { if err != nil {
fs.Stats.IncStats(StatsRecoveryMiss)
return err return err
} }
fs.Root.recoverAndclean() s.Root.recoverAndclean()
fs.Stats.IncStats(StatsRecoveryHit)
return nil return nil
} }
func (s *Store) JsonStats() []byte {
s.Stats.Watchers = uint64(s.WatcherHub.count)
return s.Stats.toJson()
}

View File

@ -1,4 +1,4 @@
package fileSystem package store
import ( import (
"math/rand" "math/rand"
@ -8,46 +8,46 @@ import (
) )
func TestCreateAndGet(t *testing.T) { func TestCreateAndGet(t *testing.T) {
fs := New() s := New()
fs.Create("/foobar", "bar", Permanent, 1, 1) s.Create("/foobar", "bar", Permanent, 1, 1)
// already exist, create should fail // already exist, create should fail
_, err := fs.Create("/foobar", "bar", Permanent, 1, 1) _, err := s.Create("/foobar", "bar", Permanent, 1, 1)
if err == nil { if err == nil {
t.Fatal("Create should fail") t.Fatal("Create should fail")
} }
fs.Delete("/foobar", true, 1, 1) s.Delete("/foobar", true, 1, 1)
// this should create successfully // this should create successfully
createAndGet(fs, "/foobar", t) createAndGet(s, "/foobar", t)
createAndGet(fs, "/foo/bar", t) createAndGet(s, "/foo/bar", t)
createAndGet(fs, "/foo/foo/bar", t) createAndGet(s, "/foo/foo/bar", t)
// meet file, create should fail // meet file, create should fail
_, err = fs.Create("/foo/bar/bar", "bar", Permanent, 2, 1) _, err = s.Create("/foo/bar/bar", "bar", Permanent, 2, 1)
if err == nil { if err == nil {
t.Fatal("Create should fail") t.Fatal("Create should fail")
} }
// create a directory // create a directory
_, err = fs.Create("/fooDir", "", Permanent, 3, 1) _, err = s.Create("/fooDir", "", Permanent, 3, 1)
if err != nil { if err != nil {
t.Fatal("Cannot create /fooDir") t.Fatal("Cannot create /fooDir")
} }
e, err := fs.Get("/fooDir", false, false, 3, 1) e, err := s.Get("/fooDir", false, false, 3, 1)
if err != nil || e.Dir != true { if err != nil || e.Dir != true {
t.Fatal("Cannot create /fooDir ") t.Fatal("Cannot create /fooDir ")
} }
// create a file under directory // create a file under directory
_, err = fs.Create("/fooDir/bar", "bar", Permanent, 4, 1) _, err = s.Create("/fooDir/bar", "bar", Permanent, 4, 1)
if err != nil { if err != nil {
t.Fatal("Cannot create /fooDir/bar = bar") t.Fatal("Cannot create /fooDir/bar = bar")
@ -56,21 +56,21 @@ func TestCreateAndGet(t *testing.T) {
} }
func TestUpdateFile(t *testing.T) { func TestUpdateFile(t *testing.T) {
fs := New() s := New()
_, err := fs.Create("/foo/bar", "bar", Permanent, 1, 1) _, err := s.Create("/foo/bar", "bar", Permanent, 1, 1)
if err != nil { if err != nil {
t.Fatalf("cannot create %s=bar [%s]", "/foo/bar", err.Error()) t.Fatalf("cannot create %s=bar [%s]", "/foo/bar", err.Error())
} }
_, err = fs.Update("/foo/bar", "barbar", Permanent, 2, 1) _, err = s.Update("/foo/bar", "barbar", Permanent, 2, 1)
if err != nil { if err != nil {
t.Fatalf("cannot update %s=barbar [%s]", "/foo/bar", err.Error()) t.Fatalf("cannot update %s=barbar [%s]", "/foo/bar", err.Error())
} }
e, err := fs.Get("/foo/bar", false, false, 2, 1) e, err := s.Get("/foo/bar", false, false, 2, 1)
if err != nil { if err != nil {
t.Fatalf("cannot get %s [%s]", "/foo/bar", err.Error()) t.Fatalf("cannot get %s [%s]", "/foo/bar", err.Error())
@ -82,37 +82,37 @@ func TestUpdateFile(t *testing.T) {
// create a directory, update its ttl, to see if it will be deleted // create a directory, update its ttl, to see if it will be deleted
_, err = fs.Create("/foo/foo", "", Permanent, 3, 1) _, err = s.Create("/foo/foo", "", Permanent, 3, 1)
if err != nil { if err != nil {
t.Fatalf("cannot create dir [%s] [%s]", "/foo/foo", err.Error()) t.Fatalf("cannot create dir [%s] [%s]", "/foo/foo", err.Error())
} }
_, err = fs.Create("/foo/foo/foo1", "bar1", Permanent, 4, 1) _, err = s.Create("/foo/foo/foo1", "bar1", Permanent, 4, 1)
if err != nil { if err != nil {
t.Fatal("cannot create [%s]", err.Error()) t.Fatal("cannot create [%s]", err.Error())
} }
_, err = fs.Create("/foo/foo/foo2", "", Permanent, 5, 1) _, err = s.Create("/foo/foo/foo2", "", Permanent, 5, 1)
if err != nil { if err != nil {
t.Fatal("cannot create [%s]", err.Error()) t.Fatal("cannot create [%s]", err.Error())
} }
_, err = fs.Create("/foo/foo/foo2/boo", "boo1", Permanent, 6, 1) _, err = s.Create("/foo/foo/foo2/boo", "boo1", Permanent, 6, 1)
if err != nil { if err != nil {
t.Fatal("cannot create [%s]", err.Error()) t.Fatal("cannot create [%s]", err.Error())
} }
expire := time.Now().Add(time.Second * 2) expire := time.Now().Add(time.Second * 2)
_, err = fs.Update("/foo/foo", "", expire, 7, 1) _, err = s.Update("/foo/foo", "", expire, 7, 1)
if err != nil { if err != nil {
t.Fatalf("cannot update dir [%s] [%s]", "/foo/foo", err.Error()) t.Fatalf("cannot update dir [%s] [%s]", "/foo/foo", err.Error())
} }
// sleep 50ms, it should still reach the node // sleep 50ms, it should still reach the node
time.Sleep(time.Microsecond * 50) time.Sleep(time.Microsecond * 50)
e, err = fs.Get("/foo/foo", true, false, 7, 1) e, err = s.Get("/foo/foo", true, false, 7, 1)
if err != nil || e.Key != "/foo/foo" { if err != nil || e.Key != "/foo/foo" {
t.Fatalf("cannot get dir before expiration [%s]", err.Error()) t.Fatalf("cannot get dir before expiration [%s]", err.Error())
@ -132,23 +132,23 @@ func TestUpdateFile(t *testing.T) {
// wait for expiration // wait for expiration
time.Sleep(time.Second * 3) time.Sleep(time.Second * 3)
e, err = fs.Get("/foo/foo", true, false, 7, 1) e, err = s.Get("/foo/foo", true, false, 7, 1)
if err == nil { if err == nil {
t.Fatal("still can get dir after expiration [%s]") t.Fatal("still can get dir after expiration [%s]")
} }
_, err = fs.Get("/foo/foo/foo1", true, false, 7, 1) _, err = s.Get("/foo/foo/foo1", true, false, 7, 1)
if err == nil { if err == nil {
t.Fatal("still can get sub node after expiration [%s]") t.Fatal("still can get sub node after expiration [%s]")
} }
_, err = fs.Get("/foo/foo/foo2", true, false, 7, 1) _, err = s.Get("/foo/foo/foo2", true, false, 7, 1)
if err == nil { if err == nil {
t.Fatal("still can get sub dir after expiration [%s]") t.Fatal("still can get sub dir after expiration [%s]")
} }
_, err = fs.Get("/foo/foo/foo2/boo", true, false, 7, 1) _, err = s.Get("/foo/foo/foo2/boo", true, false, 7, 1)
if err == nil { if err == nil {
t.Fatalf("still can get sub node of sub dir after expiration [%s]", err.Error()) t.Fatalf("still can get sub node of sub dir after expiration [%s]", err.Error())
} }
@ -156,17 +156,17 @@ func TestUpdateFile(t *testing.T) {
} }
func TestListDirectory(t *testing.T) { func TestListDirectory(t *testing.T) {
fs := New() s := New()
// create dir /foo // create dir /foo
// set key-value /foo/foo=bar // set key-value /foo/foo=bar
fs.Create("/foo/foo", "bar", Permanent, 1, 1) s.Create("/foo/foo", "bar", Permanent, 1, 1)
// create dir /foo/fooDir // create dir /foo/fooDir
// set key-value /foo/fooDir/foo=bar // set key-value /foo/fooDir/foo=bar
fs.Create("/foo/fooDir/foo", "bar", Permanent, 2, 1) s.Create("/foo/fooDir/foo", "bar", Permanent, 2, 1)
e, err := fs.Get("/foo", true, false, 2, 1) e, err := s.Get("/foo", true, false, 2, 1)
if err != nil { if err != nil {
t.Fatalf("%v", err) t.Fatalf("%v", err)
@ -191,9 +191,9 @@ func TestListDirectory(t *testing.T) {
// create dir /foo/_hidden // create dir /foo/_hidden
// set key-value /foo/_hidden/foo -> bar // set key-value /foo/_hidden/foo -> bar
fs.Create("/foo/_hidden/foo", "bar", Permanent, 3, 1) s.Create("/foo/_hidden/foo", "bar", Permanent, 3, 1)
e, _ = fs.Get("/foo", false, false, 2, 1) e, _ = s.Get("/foo", false, false, 2, 1)
if len(e.KVPairs) != 2 { if len(e.KVPairs) != 2 {
t.Fatalf("hidden node is not hidden! %s", e.KVPairs[2].Key) t.Fatalf("hidden node is not hidden! %s", e.KVPairs[2].Key)
@ -201,38 +201,38 @@ func TestListDirectory(t *testing.T) {
} }
func TestRemove(t *testing.T) { func TestRemove(t *testing.T) {
fs := New() s := New()
fs.Create("/foo", "bar", Permanent, 1, 1) s.Create("/foo", "bar", Permanent, 1, 1)
_, err := fs.Delete("/foo", false, 1, 1) _, err := s.Delete("/foo", false, 1, 1)
if err != nil { if err != nil {
t.Fatalf("cannot delete %s [%s]", "/foo", err.Error()) t.Fatalf("cannot delete %s [%s]", "/foo", err.Error())
} }
_, err = fs.Get("/foo", false, false, 1, 1) _, err = s.Get("/foo", false, false, 1, 1)
if err == nil || err.Error() != "Key Not Found" { if err == nil || err.Error() != "Key Not Found" {
t.Fatalf("can get the node after deletion") t.Fatalf("can get the node after deletion")
} }
fs.Create("/foo/bar", "bar", Permanent, 1, 1) s.Create("/foo/bar", "bar", Permanent, 1, 1)
fs.Create("/foo/car", "car", Permanent, 1, 1) s.Create("/foo/car", "car", Permanent, 1, 1)
fs.Create("/foo/dar/dar", "dar", Permanent, 1, 1) s.Create("/foo/dar/dar", "dar", Permanent, 1, 1)
_, err = fs.Delete("/foo", false, 1, 1) _, err = s.Delete("/foo", false, 1, 1)
if err == nil { if err == nil {
t.Fatalf("should not be able to delete a directory without recursive") t.Fatalf("should not be able to delete a directory without recursive")
} }
_, err = fs.Delete("/foo", true, 1, 1) _, err = s.Delete("/foo", true, 1, 1)
if err != nil { if err != nil {
t.Fatalf("cannot delete %s [%s]", "/foo", err.Error()) t.Fatalf("cannot delete %s [%s]", "/foo", err.Error())
} }
_, err = fs.Get("/foo", false, false, 1, 1) _, err = s.Get("/foo", false, false, 1, 1)
if err == nil || err.Error() != "Key Not Found" { if err == nil || err.Error() != "Key Not Found" {
t.Fatalf("can get the node after deletion ") t.Fatalf("can get the node after deletion ")
@ -241,13 +241,13 @@ func TestRemove(t *testing.T) {
} }
func TestExpire(t *testing.T) { func TestExpire(t *testing.T) {
fs := New() s := New()
expire := time.Now().Add(time.Second) expire := time.Now().Add(time.Second)
fs.Create("/foo", "bar", expire, 1, 1) s.Create("/foo", "bar", expire, 1, 1)
_, err := fs.InternalGet("/foo", 1, 1) _, err := s.InternalGet("/foo", 1, 1)
if err != nil { if err != nil {
t.Fatalf("can not get the node") t.Fatalf("can not get the node")
@ -255,7 +255,7 @@ func TestExpire(t *testing.T) {
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
_, err = fs.InternalGet("/foo", 1, 1) _, err = s.InternalGet("/foo", 1, 1)
if err == nil { if err == nil {
t.Fatalf("can get the node after expiration time") t.Fatalf("can get the node after expiration time")
@ -263,10 +263,10 @@ func TestExpire(t *testing.T) {
// test if we can reach the node before expiration // test if we can reach the node before expiration
expire = time.Now().Add(time.Second) expire = time.Now().Add(time.Second)
fs.Create("/foo", "bar", expire, 1, 1) s.Create("/foo", "bar", expire, 1, 1)
time.Sleep(time.Millisecond * 50) time.Sleep(time.Millisecond * 50)
_, err = fs.InternalGet("/foo", 1, 1) _, err = s.InternalGet("/foo", 1, 1)
if err != nil { if err != nil {
t.Fatalf("cannot get the node before expiration", err.Error()) t.Fatalf("cannot get the node before expiration", err.Error())
@ -274,8 +274,8 @@ func TestExpire(t *testing.T) {
expire = time.Now().Add(time.Second) expire = time.Now().Add(time.Second)
fs.Create("/foo", "bar", expire, 1, 1) s.Create("/foo", "bar", expire, 1, 1)
_, err = fs.Delete("/foo", false, 1, 1) _, err = s.Delete("/foo", false, 1, 1)
if err != nil { if err != nil {
t.Fatalf("cannot delete the node before expiration", err.Error()) t.Fatalf("cannot delete the node before expiration", err.Error())
@ -284,17 +284,17 @@ func TestExpire(t *testing.T) {
} }
func TestTestAndSet(t *testing.T) { // TODO prevValue == nil ? func TestTestAndSet(t *testing.T) { // TODO prevValue == nil ?
fs := New() s := New()
fs.Create("/foo", "bar", Permanent, 1, 1) s.Create("/foo", "bar", Permanent, 1, 1)
// test on wrong previous value // test on wrong previous value
_, err := fs.TestAndSet("/foo", "barbar", 0, "car", Permanent, 2, 1) _, err := s.TestAndSet("/foo", "barbar", 0, "car", Permanent, 2, 1)
if err == nil { if err == nil {
t.Fatal("test and set should fail barbar != bar") t.Fatal("test and set should fail barbar != bar")
} }
// test on value // test on value
e, err := fs.TestAndSet("/foo", "bar", 0, "car", Permanent, 3, 1) e, err := s.TestAndSet("/foo", "bar", 0, "car", Permanent, 3, 1)
if err != nil { if err != nil {
t.Fatal("test and set should succeed bar == bar") t.Fatal("test and set should succeed bar == bar")
@ -305,7 +305,7 @@ func TestTestAndSet(t *testing.T) { // TODO prevValue == nil ?
} }
// test on index // test on index
e, err = fs.TestAndSet("/foo", "", 3, "bar", Permanent, 4, 1) e, err = s.TestAndSet("/foo", "", 3, "bar", Permanent, 4, 1)
if err != nil { if err != nil {
t.Fatal("test and set should succeed index 3 == 3") t.Fatal("test and set should succeed index 3 == 3")
@ -318,61 +318,61 @@ func TestTestAndSet(t *testing.T) { // TODO prevValue == nil ?
} }
func TestWatch(t *testing.T) { func TestWatch(t *testing.T) {
fs := New() s := New()
// watch at a deeper path // watch at a deeper path
c, _ := fs.WatcherHub.watch("/foo/foo/foo", false, 0) c, _ := s.WatcherHub.watch("/foo/foo/foo", false, 0)
fs.Create("/foo/foo/foo", "bar", Permanent, 1, 1) s.Create("/foo/foo/foo", "bar", Permanent, 1, 1)
e := nonblockingRetrive(c) e := nonblockingRetrive(c)
if e.Key != "/foo/foo/foo" { if e.Key != "/foo/foo/foo" {
t.Fatal("watch for Create node fails") t.Fatal("watch for Create node fails")
} }
c, _ = fs.WatcherHub.watch("/foo/foo/foo", false, 0) c, _ = s.WatcherHub.watch("/foo/foo/foo", false, 0)
fs.Update("/foo/foo/foo", "car", Permanent, 2, 1) s.Update("/foo/foo/foo", "car", Permanent, 2, 1)
e = nonblockingRetrive(c) e = nonblockingRetrive(c)
if e.Key != "/foo/foo/foo" { if e.Key != "/foo/foo/foo" {
t.Fatal("watch for Update node fails") t.Fatal("watch for Update node fails")
} }
c, _ = fs.WatcherHub.watch("/foo/foo/foo", false, 0) c, _ = s.WatcherHub.watch("/foo/foo/foo", false, 0)
fs.TestAndSet("/foo/foo/foo", "car", 0, "bar", Permanent, 3, 1) s.TestAndSet("/foo/foo/foo", "car", 0, "bar", Permanent, 3, 1)
e = nonblockingRetrive(c) e = nonblockingRetrive(c)
if e.Key != "/foo/foo/foo" { if e.Key != "/foo/foo/foo" {
t.Fatal("watch for TestAndSet node fails") t.Fatal("watch for TestAndSet node fails")
} }
c, _ = fs.WatcherHub.watch("/foo/foo/foo", false, 0) c, _ = s.WatcherHub.watch("/foo/foo/foo", false, 0)
fs.Delete("/foo", true, 4, 1) //recursively delete s.Delete("/foo", true, 4, 1) //recursively delete
e = nonblockingRetrive(c) e = nonblockingRetrive(c)
if e.Key != "/foo" { if e.Key != "/foo" {
t.Fatal("watch for Delete node fails") t.Fatal("watch for Delete node fails")
} }
// watch at a prefix // watch at a prefix
c, _ = fs.WatcherHub.watch("/foo", true, 0) c, _ = s.WatcherHub.watch("/foo", true, 0)
fs.Create("/foo/foo/boo", "bar", Permanent, 5, 1) s.Create("/foo/foo/boo", "bar", Permanent, 5, 1)
e = nonblockingRetrive(c) e = nonblockingRetrive(c)
if e.Key != "/foo/foo/boo" { if e.Key != "/foo/foo/boo" {
t.Fatal("watch for Create subdirectory fails") t.Fatal("watch for Create subdirectory fails")
} }
c, _ = fs.WatcherHub.watch("/foo", true, 0) c, _ = s.WatcherHub.watch("/foo", true, 0)
fs.Update("/foo/foo/boo", "foo", Permanent, 6, 1) s.Update("/foo/foo/boo", "foo", Permanent, 6, 1)
e = nonblockingRetrive(c) e = nonblockingRetrive(c)
if e.Key != "/foo/foo/boo" { if e.Key != "/foo/foo/boo" {
t.Fatal("watch for Update subdirectory fails") t.Fatal("watch for Update subdirectory fails")
} }
c, _ = fs.WatcherHub.watch("/foo", true, 0) c, _ = s.WatcherHub.watch("/foo", true, 0)
fs.TestAndSet("/foo/foo/boo", "foo", 0, "bar", Permanent, 7, 1) s.TestAndSet("/foo/foo/boo", "foo", 0, "bar", Permanent, 7, 1)
e = nonblockingRetrive(c) e = nonblockingRetrive(c)
if e.Key != "/foo/foo/boo" { if e.Key != "/foo/foo/boo" {
t.Fatal("watch for TestAndSet subdirectory fails") t.Fatal("watch for TestAndSet subdirectory fails")
} }
c, _ = fs.WatcherHub.watch("/foo", true, 0) c, _ = s.WatcherHub.watch("/foo", true, 0)
fs.Delete("/foo/foo/boo", false, 8, 1) s.Delete("/foo/foo/boo", false, 8, 1)
e = nonblockingRetrive(c) e = nonblockingRetrive(c)
if e.Key != "/foo/foo/boo" { if e.Key != "/foo/foo/boo" {
t.Fatal("watch for Delete subdirectory fails") t.Fatal("watch for Delete subdirectory fails")
@ -381,14 +381,14 @@ func TestWatch(t *testing.T) {
} }
func TestSort(t *testing.T) { func TestSort(t *testing.T) {
fs := New() s := New()
// simulating random creation // simulating random creation
keys := GenKeys(80, 4) keys := GenKeys(80, 4)
i := uint64(1) i := uint64(1)
for _, k := range keys { for _, k := range keys {
_, err := fs.Create(k, "bar", Permanent, i, 1) _, err := s.Create(k, "bar", Permanent, i, 1)
if err != nil { if err != nil {
panic(err) panic(err)
} else { } else {
@ -396,7 +396,7 @@ func TestSort(t *testing.T) {
} }
} }
e, err := fs.Get("/foo", true, true, i, 1) e, err := s.Get("/foo", true, true, i, 1)
if err != nil { if err != nil {
t.Fatalf("get dir nodes failed [%s]", err.Error()) t.Fatalf("get dir nodes failed [%s]", err.Error())
} }
@ -419,14 +419,14 @@ func TestSort(t *testing.T) {
} }
func TestSaveAndRecover(t *testing.T) { func TestSaveAndRecover(t *testing.T) {
fs := New() s := New()
// simulating random creation // simulating random creation
keys := GenKeys(8, 4) keys := GenKeys(8, 4)
i := uint64(1) i := uint64(1)
for _, k := range keys { for _, k := range keys {
_, err := fs.Create(k, "bar", Permanent, i, 1) _, err := s.Create(k, "bar", Permanent, i, 1)
if err != nil { if err != nil {
panic(err) panic(err)
} else { } else {
@ -438,8 +438,8 @@ func TestSaveAndRecover(t *testing.T) {
// test if we can reach the node before expiration // test if we can reach the node before expiration
expire := time.Now().Add(time.Second) expire := time.Now().Add(time.Second)
fs.Create("/foo/foo", "bar", expire, 1, 1) s.Create("/foo/foo", "bar", expire, 1, 1)
b, err := fs.Save() b, err := s.Save()
cloneFs := New() cloneFs := New()
time.Sleep(time.Second) time.Sleep(time.Second)
@ -453,18 +453,18 @@ func TestSaveAndRecover(t *testing.T) {
} }
} }
if fs.WatcherHub.EventHistory.StartIndex != cloneFs.WatcherHub.EventHistory.StartIndex { if s.WatcherHub.EventHistory.StartIndex != cloneFs.WatcherHub.EventHistory.StartIndex {
t.Fatal("Error recovered event history start index") t.Fatal("Error recovered event history start index")
} }
for i = 0; int(i) < fs.WatcherHub.EventHistory.Queue.Size; i++ { for i = 0; int(i) < s.WatcherHub.EventHistory.Queue.Size; i++ {
if fs.WatcherHub.EventHistory.Queue.Events[i].Key != if s.WatcherHub.EventHistory.Queue.Events[i].Key !=
cloneFs.WatcherHub.EventHistory.Queue.Events[i].Key { cloneFs.WatcherHub.EventHistory.Queue.Events[i].Key {
t.Fatal("Error recovered event history") t.Fatal("Error recovered event history")
} }
} }
_, err = fs.Get("/foo/foo", false, false, 1, 1) _, err = s.Get("/foo/foo", false, false, 1, 1)
if err == nil || err.Error() != "Key Not Found" { if err == nil || err.Error() != "Key Not Found" {
t.Fatalf("can get the node after deletion ") t.Fatalf("can get the node after deletion ")
@ -488,14 +488,14 @@ func GenKeys(num int, depth int) []string {
return keys return keys
} }
func createAndGet(fs *FileSystem, path string, t *testing.T) { func createAndGet(s *Store, path string, t *testing.T) {
_, err := fs.Create(path, "bar", Permanent, 1, 1) _, err := s.Create(path, "bar", Permanent, 1, 1)
if err != nil { if err != nil {
t.Fatalf("cannot create %s=bar [%s]", path, err.Error()) t.Fatalf("cannot create %s=bar [%s]", path, err.Error())
} }
e, err := fs.Get(path, false, false, 1, 1) e, err := s.Get(path, false, false, 1, 1)
if err != nil { if err != nil {
t.Fatalf("cannot get %s [%s]", path, err.Error()) t.Fatalf("cannot get %s [%s]", path, err.Error())

View File

@ -1,16 +1,16 @@
package fileSystem package store
import ( import (
"container/list" "container/list"
"path" "path"
"strings" "strings"
"sync/atomic"
) )
type watcherHub struct { type watcherHub struct {
watchers map[string]*list.List watchers map[string]*list.List
count uint64 // current number of watchers count int64 // current number of watchers.
EventHistory *EventHistory EventHistory *EventHistory
Stats *EtcdStats
} }
type watcher struct { type watcher struct {
@ -19,11 +19,10 @@ type watcher struct {
sinceIndex uint64 sinceIndex uint64
} }
func newWatchHub(capacity int, stats *EtcdStats) *watcherHub { func newWatchHub(capacity int) *watcherHub {
return &watcherHub{ return &watcherHub{
watchers: make(map[string]*list.List), watchers: make(map[string]*list.List),
EventHistory: newEventHistory(capacity), EventHistory: newEventHistory(capacity),
Stats: stats,
} }
} }
@ -37,13 +36,9 @@ func (wh *watcherHub) watch(prefix string, recursive bool, index uint64) (<-chan
e, err := wh.EventHistory.scan(prefix, index) e, err := wh.EventHistory.scan(prefix, index)
if err != nil { if err != nil {
wh.Stats.IncStats(StatsWatchMiss)
return nil, err return nil, err
} }
wh.Stats.IncStats(StatsWatchHit)
wh.Stats.IncStats(StatsInWatchingNum)
if e != nil { if e != nil {
eventChan <- e eventChan <- e
return eventChan, nil return eventChan, nil
@ -66,6 +61,8 @@ func (wh *watcherHub) watch(prefix string, recursive bool, index uint64) (<-chan
wh.watchers[prefix] = l wh.watchers[prefix] = l
} }
atomic.AddInt64(&wh.count, 1)
return eventChan, nil return eventChan, nil
} }
@ -94,10 +91,8 @@ func (wh *watcherHub) notifyWithPath(e *Event, path string, force bool) {
if (w.recursive || force || e.Key == path) && e.Index >= w.sinceIndex { if (w.recursive || force || e.Key == path) && e.Index >= w.sinceIndex {
w.eventChan <- e w.eventChan <- e
wh.Stats.rwlock.Lock() // lock the InWatchingNum
wh.Stats.InWatchingNum--
wh.Stats.rwlock.Unlock()
l.Remove(curr) l.Remove(curr)
atomic.AddInt64(&wh.count, -1)
} else { } else {
notifiedAll = false notifiedAll = false
} }

View File

@ -1,12 +1,12 @@
package fileSystem package store
import ( import (
"testing" "testing"
) )
func TestWatcher(t *testing.T) { func TestWatcher(t *testing.T) {
fs := New() s := New()
wh := fs.WatcherHub wh := s.WatcherHub
c, err := wh.watch("/foo", true, 0) c, err := wh.watch("/foo", true, 0)
if err != nil { if err != nil {

View File

@ -15,7 +15,7 @@ import (
"time" "time"
etcdErr "github.com/coreos/etcd/error" etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/file_system" "github.com/coreos/etcd/store"
"github.com/coreos/etcd/web" "github.com/coreos/etcd/web"
"github.com/coreos/go-raft" "github.com/coreos/go-raft"
) )
@ -30,12 +30,12 @@ func durationToExpireTime(strDuration string) (time.Time, error) {
duration, err := strconv.Atoi(strDuration) duration, err := strconv.Atoi(strDuration)
if err != nil { if err != nil {
return fileSystem.Permanent, err return store.Permanent, err
} }
return time.Now().Add(time.Second * (time.Duration)(duration)), nil return time.Now().Add(time.Second * (time.Duration)(duration)), nil
} else { } else {
return fileSystem.Permanent, nil return store.Permanent, nil
} }
} }