refactor; add save and recover

This commit is contained in:
Xiang Li 2013-09-13 17:10:40 -04:00
parent a7eb09a557
commit 2c9c278e4d
7 changed files with 246 additions and 135 deletions

View File

@ -176,18 +176,18 @@ func dispatch(c Command, w http.ResponseWriter, req *http.Request, etcd bool) er
//-------------------------------------- //--------------------------------------
// Handler to return the current leader's raft address // Handler to return the current leader's raft address
// func LeaderHttpHandler(w http.ResponseWriter, req *http.Request) error { func LeaderHttpHandler(w http.ResponseWriter, req *http.Request) error {
// leader := r.Leader() leader := r.Leader()
// if leader != "" { if leader != "" {
// w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
// raftURL, _ := nameToRaftURL(leader) raftURL, _ := nameToRaftURL(leader)
// w.Write([]byte(raftURL)) w.Write([]byte(raftURL))
// return nil return nil
// } else { } else {
// return etcdErr.NewError(etcdErr.EcodeLeaderElect, "") return etcdErr.NewError(etcdErr.EcodeLeaderElect, "")
// } }
// } }
// Handler to return all the known machines in the current cluster // Handler to return all the known machines in the current cluster
func MachinesHttpHandler(w http.ResponseWriter, req *http.Request) error { func MachinesHttpHandler(w http.ResponseWriter, req *http.Request) error {

View File

@ -2,10 +2,11 @@ package fileSystem
import ( import (
"fmt" "fmt"
etcdErr "github.com/coreos/etcd/error"
"strings" "strings"
"sync" "sync"
"time" "time"
etcdErr "github.com/coreos/etcd/error"
) )
const ( const (
@ -61,26 +62,26 @@ func newEvent(action string, key string, index uint64, term uint64) *Event {
} }
type eventQueue struct { type eventQueue struct {
events []*Event Events []*Event
size int Size int
front int Front int
capacity int Capacity int
} }
func (eq *eventQueue) back() int { func (eq *eventQueue) back() int {
return (eq.front + eq.size - 1 + eq.capacity) % eq.capacity return (eq.Front + eq.Size - 1 + eq.Capacity) % eq.Capacity
} }
func (eq *eventQueue) insert(e *Event) { func (eq *eventQueue) insert(e *Event) {
index := (eq.back() + 1) % eq.capacity index := (eq.back() + 1) % eq.Capacity
eq.events[index] = e eq.Events[index] = e
if eq.size == eq.capacity { //dequeue if eq.Size == eq.Capacity { //dequeue
eq.front = (index + 1) % eq.capacity eq.Front = (index + 1) % eq.Capacity
} else { } else {
eq.size++ eq.Size++
} }
} }
@ -94,8 +95,8 @@ type EventHistory struct {
func newEventHistory(capacity int) *EventHistory { func newEventHistory(capacity int) *EventHistory {
return &EventHistory{ return &EventHistory{
Queue: eventQueue{ Queue: eventQueue{
capacity: capacity, Capacity: capacity,
events: make([]*Event, capacity), Events: make([]*Event, capacity),
}, },
} }
} }
@ -107,7 +108,7 @@ func (eh *EventHistory) addEvent(e *Event) {
eh.Queue.insert(e) eh.Queue.insert(e)
eh.StartIndex = eh.Queue.events[eh.Queue.front].Index eh.StartIndex = eh.Queue.Events[eh.Queue.Front].Index
} }
// scan function is enumerating events from the index in history and // scan function is enumerating events from the index in history and
@ -129,19 +130,19 @@ func (eh *EventHistory) scan(prefix string, index uint64) (*Event, error) {
) )
} }
if start >= uint64(eh.Queue.size) { if start >= uint64(eh.Queue.Size) {
return nil, nil return nil, nil
} }
i := int((start + uint64(eh.Queue.front)) % uint64(eh.Queue.capacity)) i := int((start + uint64(eh.Queue.Front)) % uint64(eh.Queue.Capacity))
for { for {
e := eh.Queue.events[i] e := eh.Queue.Events[i]
if strings.HasPrefix(e.Key, prefix) { if strings.HasPrefix(e.Key, prefix) {
return e, nil return e, nil
} }
i = (i + 1) % eh.Queue.capacity i = (i + 1) % eh.Queue.Capacity
if i == eh.Queue.back() { if i == eh.Queue.back() {
// TODO: Add error type // TODO: Add error type

View File

@ -19,15 +19,15 @@ func TestEventQueue(t *testing.T) {
// Test // Test
j := 100 j := 100
i := eh.Queue.front i := eh.Queue.Front
n := eh.Queue.size n := eh.Queue.Size
for ; n > 0; n-- { for ; n > 0; n-- {
e := eh.Queue.events[i] e := eh.Queue.Events[i]
if e.Index != uint64(j) { if e.Index != uint64(j) {
t.Fatalf("queue error!") t.Fatalf("queue error!")
} }
j++ j++
i = (i + 1) % eh.Queue.capacity i = (i + 1) % eh.Queue.Capacity
} }

View File

@ -1,6 +1,7 @@
package fileSystem package fileSystem
import ( import (
"encoding/json"
"fmt" "fmt"
"path" "path"
"sort" "sort"
@ -11,11 +12,10 @@ import (
) )
type FileSystem struct { type FileSystem struct {
Root *Node Root *Node
EventHistory *EventHistory WatcherHub *watcherHub
WatcherHub *watcherHub Index uint64
Index uint64 Term uint64
Term uint64
} }
func New() *FileSystem { func New() *FileSystem {
@ -126,7 +126,7 @@ func (fs *FileSystem) Create(nodePath string, value string, expireTime time.Time
// Node with TTL // Node with TTL
if expireTime != Permanent { if expireTime != Permanent {
go n.Expire() n.Expire()
e.Expiration = &n.ExpireTime e.Expiration = &n.ExpireTime
e.TTL = int64(expireTime.Sub(time.Now()) / time.Second) e.TTL = int64(expireTime.Sub(time.Now()) / time.Second)
} }
@ -164,13 +164,13 @@ func (fs *FileSystem) Update(nodePath string, value string, expireTime time.Time
} }
// update ttl // update ttl
if n.ExpireTime != Permanent && expireTime != Permanent { if !n.IsPermanent() && expireTime != Permanent {
n.stopExpire <- true n.stopExpire <- true
} }
if expireTime != Permanent { if expireTime.Sub(Permanent) != 0 {
n.ExpireTime = expireTime n.ExpireTime = expireTime
go n.Expire() n.Expire()
e.Expiration = &n.ExpireTime e.Expiration = &n.ExpireTime
e.TTL = int64(expireTime.Sub(time.Now()) / time.Second) e.TTL = int64(expireTime.Sub(time.Now()) / time.Second)
} }
@ -298,7 +298,6 @@ func (fs *FileSystem) InternalGet(nodePath string, index uint64, term uint64) (*
// 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 (fs *FileSystem) checkDir(parent *Node, dirName string) (*Node, error) {
subDir, ok := parent.Children[dirName] subDir, ok := parent.Children[dirName]
if ok { if ok {
@ -311,3 +310,35 @@ func (fs *FileSystem) checkDir(parent *Node, dirName string) (*Node, error) {
return n, nil return n, nil
} }
// Save function saves the static state of the store system.
// 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
// be cyclic dependencies issue for the json package.
func (fs *FileSystem) Save() []byte {
cloneFs := New()
cloneFs.Root = fs.Root.Clone()
b, err := json.Marshal(fs)
if err != nil {
panic(err)
}
return b
}
// recovery function recovery the store system from a static state.
// It needs to recovery the parent field of the nodes.
// It needs to delete the expired nodes since the saved time and also
// need to create monitor go routines.
func (fs *FileSystem) Recover(state []byte) {
err := json.Unmarshal(state, fs)
if err != nil {
panic(err)
}
fs.Root.recoverAndclean()
}

View File

@ -311,7 +311,6 @@ func TestTestAndSet(t *testing.T) { // TODO prevValue == nil ?
t.Fatalf("[%v/%v] [%v/%v]", e.PrevValue, "car", e.Value, "bar") t.Fatalf("[%v/%v] [%v/%v]", e.PrevValue, "car", e.Value, "bar")
} }
//e, err = fs.TestAndSet("/foo", )
} }
func TestWatch(t *testing.T) { func TestWatch(t *testing.T) {
@ -377,6 +376,115 @@ func TestWatch(t *testing.T) {
} }
func TestSort(t *testing.T) {
fs := New()
// simulating random creation
keys := GenKeys(80, 4)
i := uint64(1)
for _, k := range keys {
_, err := fs.Create(k, "bar", Permanent, i, 1)
if err != nil {
panic(err)
} else {
i++
}
}
e, err := fs.Get("/foo", true, true, i, 1)
if err != nil {
t.Fatalf("get dir nodes failed [%s]", err.Error())
}
for i, k := range e.KVPairs[:len(e.KVPairs)-1] {
if k.Key >= e.KVPairs[i+1].Key {
t.Fatalf("sort failed, [%s] should be placed after [%s]", k.Key, e.KVPairs[i+1].Key)
}
if k.Dir {
recursiveTestSort(k, t)
}
}
if k := e.KVPairs[len(e.KVPairs)-1]; k.Dir {
recursiveTestSort(k, t)
}
}
func TestSaveAndRecover(t *testing.T) {
fs := New()
// simulating random creation
keys := GenKeys(8, 4)
i := uint64(1)
for _, k := range keys {
_, err := fs.Create(k, "bar", Permanent, i, 1)
if err != nil {
panic(err)
} else {
i++
}
}
// create a node with expiration
// test if we can reach the node before expiration
expire := time.Now().Add(time.Second)
fs.Create("/foo/foo", "bar", expire, 1, 1)
b := fs.Save()
cloneFs := New()
time.Sleep(time.Second)
cloneFs.Recover(b)
for i, k := range keys {
_, err := cloneFs.Get(k, false, false, uint64(i), 1)
if err != nil {
panic(err)
}
}
if fs.WatcherHub.EventHistory.StartIndex != cloneFs.WatcherHub.EventHistory.StartIndex {
t.Fatal("Error recovered event history start index")
}
for i = 0; int(i) < fs.WatcherHub.EventHistory.Queue.Size; i++ {
if fs.WatcherHub.EventHistory.Queue.Events[i].Key !=
cloneFs.WatcherHub.EventHistory.Queue.Events[i].Key {
t.Fatal("Error recovered event history")
}
}
_, err := fs.Get("/foo/foo", false, false, 1, 1)
if err == nil || err.Error() != "Key Not Found" {
t.Fatalf("can get the node after deletion ")
}
}
// GenKeys randomly generate num of keys with max depth
func GenKeys(num int, depth int) []string {
keys := make([]string, num)
for i := 0; i < num; i++ {
keys[i] = "/foo"
depth := rand.Intn(depth) + 1
for j := 0; j < depth; j++ {
keys[i] += "/" + strconv.Itoa(rand.Int())
}
}
return keys
}
func createAndGet(fs *FileSystem, path string, t *testing.T) { func createAndGet(fs *FileSystem, path string, t *testing.T) {
_, err := fs.Create(path, "bar", Permanent, 1, 1) _, err := fs.Create(path, "bar", Permanent, 1, 1)
@ -396,58 +504,8 @@ func createAndGet(fs *FileSystem, path string, t *testing.T) {
} }
func nonblockingRetrive(c <-chan *Event) *Event {
select {
case e := <-c:
return e
default:
return nil
}
}
func TestSort(t *testing.T) {
fs := New()
// simulating random creation
keys := GenKeys(80, 4)
//t.Log(keys)
i := uint64(1)
for _, k := range keys {
_, err := fs.Create(k, "bar", Permanent, i, 1)
if err != nil {
//t.Logf("create node[%s] failed %s", k, err.Error())
} else {
i++
}
}
e, err := fs.Get("/foo", true, true, i, 1)
if err != nil {
t.Fatalf("get dir nodes failed [%s]", err.Error())
}
for i, k := range e.KVPairs[:len(e.KVPairs)-1] {
//t.Log("root:")
//t.Log(k)
if k.Key >= e.KVPairs[i+1].Key {
t.Fatalf("sort failed, [%s] should be placed after [%s]", k.Key, e.KVPairs[i+1].Key)
}
if k.Dir {
recursiveTestSort(k, t)
}
}
if k := e.KVPairs[len(e.KVPairs)-1]; k.Dir {
recursiveTestSort(k, t)
}
}
func recursiveTestSort(k KeyValuePair, t *testing.T) { func recursiveTestSort(k KeyValuePair, t *testing.T) {
//t.Log("recursive in")
//t.Log(k)
for i, v := range k.KVPairs[:len(k.KVPairs)-1] { for i, v := range k.KVPairs[:len(k.KVPairs)-1] {
if v.Key >= k.KVPairs[i+1].Key { if v.Key >= k.KVPairs[i+1].Key {
t.Fatalf("sort failed, [%s] should be placed after [%s]", v.Key, k.KVPairs[i+1].Key) t.Fatalf("sort failed, [%s] should be placed after [%s]", v.Key, k.KVPairs[i+1].Key)
@ -464,17 +522,11 @@ func recursiveTestSort(k KeyValuePair, t *testing.T) {
} }
} }
// GenKeys randomly generate num of keys with max depth func nonblockingRetrive(c <-chan *Event) *Event {
func GenKeys(num int, depth int) []string { select {
keys := make([]string, num) case e := <-c:
for i := 0; i < num; i++ { return e
default:
keys[i] = "/foo" return nil
depth := rand.Intn(depth) + 1
for j := 0; j < depth; j++ {
keys[i] += "/" + strconv.Itoa(rand.Int()%20)
}
} }
return keys
} }

View File

@ -1,7 +1,6 @@
package fileSystem package fileSystem
import ( import (
"fmt"
"path" "path"
"sort" "sort"
"sync" "sync"
@ -25,7 +24,7 @@ type Node struct {
CreateTerm uint64 CreateTerm uint64
ModifiedIndex uint64 ModifiedIndex uint64
ModifiedTerm uint64 ModifiedTerm uint64
Parent *Node Parent *Node `json:"-"`
ExpireTime time.Time ExpireTime time.Time
ACL string ACL string
Value string // for key-value pair Value string // for key-value pair
@ -237,62 +236,89 @@ func (n *Node) Clone() *Node {
return clone return clone
} }
// IsDir function checks whether the node is a directory. func (n *Node) recoverAndclean() {
// If the node is a directory, the function will return true. if n.IsDir() {
// Otherwise the function will return false. for _, child := range n.Children {
func (n *Node) IsDir() bool { child.Parent = n
child.recoverAndclean()
if n.Children == nil { // key-value pair }
return false
} }
return true n.stopExpire = make(chan bool, 1)
n.Expire()
} }
func (n *Node) Expire() { func (n *Node) Expire() {
duration := n.ExpireTime.Sub(time.Now()) expired, duration := n.IsExpired()
if duration <= 0 {
if expired { // has been expired
n.Remove(true, nil) n.Remove(true, nil)
return return
} }
select { if duration == 0 { // Permanent Node
// if timeout, delete the node
case <-time.After(duration):
n.Remove(true, nil)
return return
// if stopped, return
case <-n.stopExpire:
fmt.Println("expire stopped")
return
} }
go func() { // do monitoring
select {
// if timeout, delete the node
case <-time.After(duration):
n.Remove(true, nil)
return
// if stopped, return
case <-n.stopExpire:
return
}
}()
} }
// IsHidden function checks if the node is a hidden node. A hidden node // IsHidden function checks if the node is a hidden node. A hidden node
// will begin with '_' // will begin with '_'
// A hidden node will not be shown via get command under a directory // A hidden node will not be shown via get command under a directory
// For example if we have /foo/_hidden and /foo/notHidden, get "/foo" // For example if we have /foo/_hidden and /foo/notHidden, get "/foo"
// will only return /foo/notHidden // will only return /foo/notHidden
func (n *Node) IsHidden() bool { func (n *Node) IsHidden() bool {
_, name := path.Split(n.Path) _, name := path.Split(n.Path)
if name[0] == '_' { //hidden return name[0] == '_'
return true
}
func (n *Node) IsPermanent() bool {
return n.ExpireTime.Sub(Permanent) == 0
}
func (n *Node) IsExpired() (bool, time.Duration) {
if n.IsPermanent() {
return false, 0
} }
return false duration := n.ExpireTime.Sub(time.Now())
if duration <= 0 {
return true, 0
}
return false, duration
}
// IsDir function checks whether the node is a directory.
// If the node is a directory, the function will return true.
// Otherwise the function will return false.
func (n *Node) IsDir() bool {
return !(n.Children == nil)
} }
func (n *Node) Pair(recurisive, sorted bool) KeyValuePair { func (n *Node) Pair(recurisive, sorted bool) KeyValuePair {
if n.IsDir() { if n.IsDir() {
pair := KeyValuePair{ pair := KeyValuePair{
Key: n.Path, Key: n.Path,
Dir: true, Dir: true,
} }
if !recurisive { if !recurisive {
return pair return pair
} }

View File

@ -99,7 +99,6 @@ func (wh *watcherHub) notifyWithPath(e *Event, path string, force bool) {
} }
func (wh *watcherHub) notify(e *Event) { func (wh *watcherHub) notify(e *Event) {
segments := strings.Split(e.Key, "/") segments := strings.Split(e.Key, "/")
currPath := "/" currPath := "/"
@ -109,4 +108,6 @@ func (wh *watcherHub) notify(e *Event) {
currPath = path.Join(currPath, segment) currPath = path.Join(currPath, segment)
wh.notifyWithPath(e, currPath, false) wh.notifyWithPath(e, currPath, false)
} }
wh.EventHistory.addEvent(e)
} }