mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
Merge pull request #343 from xiangli-cmu/refactoring_event
Refactoring event
This commit is contained in:
commit
73f04d5cec
@ -70,7 +70,7 @@ func (r *Registry) Count() int {
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return len(e.KVPairs)
|
||||
return len(e.Node.Nodes)
|
||||
}
|
||||
|
||||
// Retrieves the client URL for a given node by name.
|
||||
@ -135,7 +135,7 @@ func (r *Registry) urls(leaderName, selfName string, url func(name string) (stri
|
||||
// Retrieve a list of all nodes.
|
||||
if e, _ := r.store.Get(RegistryKey, false, false); e != nil {
|
||||
// Lookup the URL for each one.
|
||||
for _, pair := range e.KVPairs {
|
||||
for _, pair := range e.Node.Nodes {
|
||||
_, name := filepath.Split(pair.Key)
|
||||
if url, _ := url(name); len(url) > 0 && name != leaderName {
|
||||
urls = append(urls, url)
|
||||
@ -166,7 +166,7 @@ func (r *Registry) load(name string) {
|
||||
}
|
||||
|
||||
// Parse as a query string.
|
||||
m, err := url.ParseQuery(e.Value)
|
||||
m, err := url.ParseQuery(e.Node.Value)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to parse peers entry: %s", name))
|
||||
}
|
||||
|
@ -24,6 +24,6 @@ func TestV2DeleteKey(t *testing.T) {
|
||||
resp, err = tests.DeleteForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), url.Values{})
|
||||
body := tests.ReadBody(resp)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, string(body), `{"action":"delete","key":"/foo/bar","prevValue":"XXX","modifiedIndex":2}`, "")
|
||||
assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo/bar","prevValue":"XXX","modifiedIndex":2,"createdIndex":1}}`, "")
|
||||
})
|
||||
}
|
||||
|
@ -25,9 +25,11 @@ func TestV2GetKey(t *testing.T) {
|
||||
resp, _ = tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"))
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["action"], "get", "")
|
||||
assert.Equal(t, body["key"], "/foo/bar", "")
|
||||
assert.Equal(t, body["value"], "XXX", "")
|
||||
assert.Equal(t, body["modifiedIndex"], 1, "")
|
||||
|
||||
node := body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["key"], "/foo/bar", "")
|
||||
assert.Equal(t, node["value"], "XXX", "")
|
||||
assert.Equal(t, node["modifiedIndex"], 1, "")
|
||||
})
|
||||
}
|
||||
|
||||
@ -52,23 +54,25 @@ func TestV2GetKeyRecursively(t *testing.T) {
|
||||
resp, _ = tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo?recursive=true"))
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["action"], "get", "")
|
||||
assert.Equal(t, body["key"], "/foo", "")
|
||||
assert.Equal(t, body["dir"], true, "")
|
||||
assert.Equal(t, body["modifiedIndex"], 1, "")
|
||||
assert.Equal(t, len(body["kvs"].([]interface{})), 2, "")
|
||||
|
||||
kv0 := body["kvs"].([]interface{})[0].(map[string]interface{})
|
||||
assert.Equal(t, kv0["key"], "/foo/x", "")
|
||||
assert.Equal(t, kv0["value"], "XXX", "")
|
||||
assert.Equal(t, kv0["ttl"], 10, "")
|
||||
node := body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["key"], "/foo", "")
|
||||
assert.Equal(t, node["dir"], true, "")
|
||||
assert.Equal(t, node["modifiedIndex"], 1, "")
|
||||
assert.Equal(t, len(node["nodes"].([]interface{})), 2, "")
|
||||
|
||||
kv1 := body["kvs"].([]interface{})[1].(map[string]interface{})
|
||||
assert.Equal(t, kv1["key"], "/foo/y", "")
|
||||
assert.Equal(t, kv1["dir"], true, "")
|
||||
node0 := node["nodes"].([]interface{})[0].(map[string]interface{})
|
||||
assert.Equal(t, node0["key"], "/foo/x", "")
|
||||
assert.Equal(t, node0["value"], "XXX", "")
|
||||
assert.Equal(t, node0["ttl"], 10, "")
|
||||
|
||||
kvs2 := kv1["kvs"].([]interface{})[0].(map[string]interface{})
|
||||
assert.Equal(t, kvs2["key"], "/foo/y/z", "")
|
||||
assert.Equal(t, kvs2["value"], "YYY", "")
|
||||
node1 := node["nodes"].([]interface{})[1].(map[string]interface{})
|
||||
assert.Equal(t, node1["key"], "/foo/y", "")
|
||||
assert.Equal(t, node1["dir"], true, "")
|
||||
|
||||
node2 := node1["nodes"].([]interface{})[0].(map[string]interface{})
|
||||
assert.Equal(t, node2["key"], "/foo/y/z", "")
|
||||
assert.Equal(t, node2["value"], "YYY", "")
|
||||
})
|
||||
}
|
||||
|
||||
@ -109,9 +113,11 @@ func TestV2WatchKey(t *testing.T) {
|
||||
|
||||
assert.NotNil(t, body, "")
|
||||
assert.Equal(t, body["action"], "set", "")
|
||||
assert.Equal(t, body["key"], "/foo/bar", "")
|
||||
assert.Equal(t, body["value"], "XXX", "")
|
||||
assert.Equal(t, body["modifiedIndex"], 1, "")
|
||||
|
||||
node := body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["key"], "/foo/bar", "")
|
||||
assert.Equal(t, node["value"], "XXX", "")
|
||||
assert.Equal(t, node["modifiedIndex"], 1, "")
|
||||
})
|
||||
}
|
||||
|
||||
@ -162,8 +168,10 @@ func TestV2WatchKeyWithIndex(t *testing.T) {
|
||||
|
||||
assert.NotNil(t, body, "")
|
||||
assert.Equal(t, body["action"], "set", "")
|
||||
assert.Equal(t, body["key"], "/foo/bar", "")
|
||||
assert.Equal(t, body["value"], "YYY", "")
|
||||
assert.Equal(t, body["modifiedIndex"], 2, "")
|
||||
|
||||
node := body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["key"], "/foo/bar", "")
|
||||
assert.Equal(t, node["value"], "YYY", "")
|
||||
assert.Equal(t, node["modifiedIndex"], 2, "")
|
||||
})
|
||||
}
|
||||
|
@ -21,18 +21,22 @@ func TestV2CreateUnique(t *testing.T) {
|
||||
resp, _ := tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["action"], "create", "")
|
||||
assert.Equal(t, body["key"], "/foo/bar/1", "")
|
||||
assert.Equal(t, body["dir"], true, "")
|
||||
assert.Equal(t, body["modifiedIndex"], 1, "")
|
||||
|
||||
node := body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["key"], "/foo/bar/1", "")
|
||||
assert.Equal(t, node["dir"], true, "")
|
||||
assert.Equal(t, node["modifiedIndex"], 1, "")
|
||||
|
||||
// Second POST should add next index to list.
|
||||
resp, _ = tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
|
||||
body = tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["key"], "/foo/bar/2", "")
|
||||
node = body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["key"], "/foo/bar/2", "")
|
||||
|
||||
// POST to a different key should add index to that list.
|
||||
resp, _ = tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/baz"), nil)
|
||||
body = tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["key"], "/foo/baz/3", "")
|
||||
node = body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["key"], "/foo/baz/3", "")
|
||||
})
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ func TestV2SetKey(t *testing.T) {
|
||||
resp, err := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
|
||||
body := tests.ReadBody(resp)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, string(body), `{"action":"set","key":"/foo/bar","value":"XXX","modifiedIndex":1}`, "")
|
||||
assert.Equal(t, string(body), `{"action":"set","node":{"key":"/foo/bar","value":"XXX","modifiedIndex":1,"createdIndex":1}}`, "")
|
||||
})
|
||||
}
|
||||
|
||||
@ -38,10 +38,11 @@ func TestV2SetKeyWithTTL(t *testing.T) {
|
||||
v.Set("ttl", "20")
|
||||
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["ttl"], 20, "")
|
||||
node := body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["ttl"], 20, "")
|
||||
|
||||
// Make sure the expiration date is correct.
|
||||
expiration, _ := time.Parse(time.RFC3339Nano, body["expiration"].(string))
|
||||
expiration, _ := time.Parse(time.RFC3339Nano, node["expiration"].(string))
|
||||
assert.Equal(t, expiration.Sub(t0)/time.Second, 20, "")
|
||||
})
|
||||
}
|
||||
@ -74,7 +75,8 @@ func TestV2CreateKeySuccess(t *testing.T) {
|
||||
v.Set("prevExist", "false")
|
||||
resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["value"], "XXX", "")
|
||||
node := body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["value"], "XXX", "")
|
||||
})
|
||||
}
|
||||
|
||||
@ -116,7 +118,9 @@ func TestV2UpdateKeySuccess(t *testing.T) {
|
||||
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["action"], "update", "")
|
||||
assert.Equal(t, body["prevValue"], "XXX", "")
|
||||
|
||||
node := body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["prevValue"], "XXX", "")
|
||||
})
|
||||
}
|
||||
|
||||
@ -173,9 +177,11 @@ func TestV2SetKeyCASOnIndexSuccess(t *testing.T) {
|
||||
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["action"], "compareAndSwap", "")
|
||||
assert.Equal(t, body["prevValue"], "XXX", "")
|
||||
assert.Equal(t, body["value"], "YYY", "")
|
||||
assert.Equal(t, body["modifiedIndex"], 2, "")
|
||||
|
||||
node := body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["prevValue"], "XXX", "")
|
||||
assert.Equal(t, node["value"], "YYY", "")
|
||||
assert.Equal(t, node["modifiedIndex"], 2, "")
|
||||
})
|
||||
}
|
||||
|
||||
@ -234,9 +240,11 @@ func TestV2SetKeyCASOnValueSuccess(t *testing.T) {
|
||||
resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
|
||||
body := tests.ReadBodyJSON(resp)
|
||||
assert.Equal(t, body["action"], "compareAndSwap", "")
|
||||
assert.Equal(t, body["prevValue"], "XXX", "")
|
||||
assert.Equal(t, body["value"], "YYY", "")
|
||||
assert.Equal(t, body["modifiedIndex"], 2, "")
|
||||
|
||||
node := body["node"].(map[string]interface{})
|
||||
assert.Equal(t, node["prevValue"], "XXX", "")
|
||||
assert.Equal(t, node["value"], "YYY", "")
|
||||
assert.Equal(t, node["modifiedIndex"], 2, "")
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,5 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
Get = "get"
|
||||
Create = "create"
|
||||
@ -15,22 +11,20 @@ const (
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
Action string `json:"action"`
|
||||
Key string `json:"key, omitempty"`
|
||||
Dir bool `json:"dir,omitempty"`
|
||||
PrevValue string `json:"prevValue,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
KVPairs kvPairs `json:"kvs,omitempty"`
|
||||
Expiration *time.Time `json:"expiration,omitempty"`
|
||||
TTL int64 `json:"ttl,omitempty"` // Time to live in second
|
||||
ModifiedIndex uint64 `json:"modifiedIndex"`
|
||||
Action string `json:"action"`
|
||||
Node *NodeExtern `json:"node,omitempty"`
|
||||
}
|
||||
|
||||
func newEvent(action string, key string, index uint64) *Event {
|
||||
return &Event{
|
||||
Action: action,
|
||||
func newEvent(action string, key string, modifiedIndex, createdIndex uint64) *Event {
|
||||
n := &NodeExtern{
|
||||
Key: key,
|
||||
ModifiedIndex: index,
|
||||
ModifiedIndex: modifiedIndex,
|
||||
CreatedIndex: createdIndex,
|
||||
}
|
||||
|
||||
return &Event{
|
||||
Action: action,
|
||||
Node: n,
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +33,7 @@ func (e *Event) IsCreated() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if e.Action == Set && e.PrevValue == "" {
|
||||
if e.Action == Set && e.Node.PrevValue == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -47,20 +41,20 @@ func (e *Event) IsCreated() bool {
|
||||
}
|
||||
|
||||
func (e *Event) Index() uint64 {
|
||||
return e.ModifiedIndex
|
||||
return e.Node.ModifiedIndex
|
||||
}
|
||||
|
||||
// Converts an event object into a response object.
|
||||
func (event *Event) Response() interface{} {
|
||||
if !event.Dir {
|
||||
if !event.Node.Dir {
|
||||
response := &Response{
|
||||
Action: event.Action,
|
||||
Key: event.Key,
|
||||
Value: event.Value,
|
||||
PrevValue: event.PrevValue,
|
||||
Index: event.ModifiedIndex,
|
||||
TTL: event.TTL,
|
||||
Expiration: event.Expiration,
|
||||
Key: event.Node.Key,
|
||||
Value: event.Node.Value,
|
||||
PrevValue: event.Node.PrevValue,
|
||||
Index: event.Node.ModifiedIndex,
|
||||
TTL: event.Node.TTL,
|
||||
Expiration: event.Node.Expiration,
|
||||
}
|
||||
|
||||
if response.Action == Set {
|
||||
@ -75,15 +69,15 @@ func (event *Event) Response() interface{} {
|
||||
|
||||
return response
|
||||
} else {
|
||||
responses := make([]*Response, len(event.KVPairs))
|
||||
responses := make([]*Response, len(event.Node.Nodes))
|
||||
|
||||
for i, kv := range event.KVPairs {
|
||||
for i, node := range event.Node.Nodes {
|
||||
responses[i] = &Response{
|
||||
Action: event.Action,
|
||||
Key: kv.Key,
|
||||
Value: kv.Value,
|
||||
Dir: kv.Dir,
|
||||
Index: event.ModifiedIndex,
|
||||
Key: node.Key,
|
||||
Value: node.Value,
|
||||
Dir: node.Dir,
|
||||
Index: node.ModifiedIndex,
|
||||
}
|
||||
}
|
||||
return responses
|
||||
|
@ -34,7 +34,7 @@ func (eh *EventHistory) addEvent(e *Event) *Event {
|
||||
|
||||
eh.LastIndex = e.Index()
|
||||
|
||||
eh.StartIndex = eh.Queue.Events[eh.Queue.Front].ModifiedIndex
|
||||
eh.StartIndex = eh.Queue.Events[eh.Queue.Front].Index()
|
||||
|
||||
return e
|
||||
}
|
||||
@ -63,7 +63,7 @@ func (eh *EventHistory) scan(key string, recursive bool, index uint64) (*Event,
|
||||
for {
|
||||
e := eh.Queue.Events[i]
|
||||
|
||||
ok := (e.Key == key)
|
||||
ok := (e.Node.Key == key)
|
||||
|
||||
if recursive {
|
||||
// add tailing slash
|
||||
@ -72,7 +72,7 @@ func (eh *EventHistory) scan(key string, recursive bool, index uint64) (*Event,
|
||||
key = key + "/"
|
||||
}
|
||||
|
||||
ok = ok || strings.HasPrefix(e.Key, key)
|
||||
ok = ok || strings.HasPrefix(e.Node.Key, key)
|
||||
}
|
||||
|
||||
if ok && index <= e.Index() { // make sure we bypass the smaller one
|
||||
|
@ -13,7 +13,7 @@ func TestEventQueue(t *testing.T) {
|
||||
|
||||
// Add
|
||||
for i := 0; i < 200; i++ {
|
||||
e := newEvent(Create, "/foo", uint64(i))
|
||||
e := newEvent(Create, "/foo", uint64(i), uint64(i))
|
||||
eh.addEvent(e)
|
||||
}
|
||||
|
||||
@ -35,11 +35,11 @@ func TestScanHistory(t *testing.T) {
|
||||
eh := newEventHistory(100)
|
||||
|
||||
// Add
|
||||
eh.addEvent(newEvent(Create, "/foo", 1))
|
||||
eh.addEvent(newEvent(Create, "/foo/bar", 2))
|
||||
eh.addEvent(newEvent(Create, "/foo/foo", 3))
|
||||
eh.addEvent(newEvent(Create, "/foo/bar/bar", 4))
|
||||
eh.addEvent(newEvent(Create, "/foo/foo/foo", 5))
|
||||
eh.addEvent(newEvent(Create, "/foo", 1, 1))
|
||||
eh.addEvent(newEvent(Create, "/foo/bar", 2, 2))
|
||||
eh.addEvent(newEvent(Create, "/foo/foo", 3, 3))
|
||||
eh.addEvent(newEvent(Create, "/foo/bar/bar", 4, 4))
|
||||
eh.addEvent(newEvent(Create, "/foo/foo/foo", 5, 5))
|
||||
|
||||
e, err := eh.scan("/foo", false, 1)
|
||||
if err != nil || e.Index() != 1 {
|
||||
|
@ -33,7 +33,7 @@ func TestHeapPushPop(t *testing.T) {
|
||||
func TestHeapUpdate(t *testing.T) {
|
||||
h := newTtlKeyHeap()
|
||||
|
||||
kvs := make([]*Node, 10)
|
||||
kvs := make([]*node, 10)
|
||||
|
||||
// add from older expire time to earlier expire time
|
||||
// the path is equal to ttl from now
|
||||
|
@ -1,31 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// When user list a directory, we add all the node into key-value pair slice
|
||||
type KeyValuePair struct {
|
||||
Key string `json:"key, omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Dir bool `json:"dir,omitempty"`
|
||||
Expiration *time.Time `json:"expiration,omitempty"`
|
||||
TTL int64 `json:"ttl,omitempty"` // Time to live in second
|
||||
KVPairs kvPairs `json:"kvs,omitempty"`
|
||||
ModifiedIndex uint64 `json:"modifiedIndex,omitempty"`
|
||||
}
|
||||
|
||||
type kvPairs []KeyValuePair
|
||||
|
||||
// interfaces for sorting
|
||||
func (kvs kvPairs) Len() int {
|
||||
return len(kvs)
|
||||
}
|
||||
|
||||
func (kvs kvPairs) Less(i, j int) bool {
|
||||
return kvs[i].Key < kvs[j].Key
|
||||
}
|
||||
|
||||
func (kvs kvPairs) Swap(i, j int) {
|
||||
kvs[i], kvs[j] = kvs[j], kvs[i]
|
||||
}
|
@ -10,34 +10,34 @@ import (
|
||||
|
||||
var Permanent time.Time
|
||||
|
||||
// Node is the basic element in the store system.
|
||||
// node is the basic element in the store system.
|
||||
// A key-value pair will have a string value
|
||||
// A directory will have a children map
|
||||
type Node struct {
|
||||
type node struct {
|
||||
Path string
|
||||
|
||||
CreateIndex uint64
|
||||
CreatedIndex uint64
|
||||
ModifiedIndex uint64
|
||||
|
||||
Parent *Node `json:"-"` // should not encode this field! avoid circular dependency.
|
||||
Parent *node `json:"-"` // should not encode this field! avoid circular dependency.
|
||||
|
||||
ExpireTime time.Time
|
||||
ACL string
|
||||
Value string // for key-value pair
|
||||
Children map[string]*Node // for directory
|
||||
Children map[string]*node // for directory
|
||||
|
||||
// A reference to the store this node is attached to.
|
||||
store *store
|
||||
}
|
||||
|
||||
// newKV creates a Key-Value pair
|
||||
func newKV(store *store, nodePath string, value string, createIndex uint64,
|
||||
parent *Node, ACL string, expireTime time.Time) *Node {
|
||||
func newKV(store *store, nodePath string, value string, createdIndex uint64,
|
||||
parent *node, ACL string, expireTime time.Time) *node {
|
||||
|
||||
return &Node{
|
||||
return &node{
|
||||
Path: nodePath,
|
||||
CreateIndex: createIndex,
|
||||
ModifiedIndex: createIndex,
|
||||
CreatedIndex: createdIndex,
|
||||
ModifiedIndex: createdIndex,
|
||||
Parent: parent,
|
||||
ACL: ACL,
|
||||
store: store,
|
||||
@ -47,17 +47,17 @@ func newKV(store *store, nodePath string, value string, createIndex uint64,
|
||||
}
|
||||
|
||||
// newDir creates a directory
|
||||
func newDir(store *store, nodePath string, createIndex uint64, parent *Node,
|
||||
ACL string, expireTime time.Time) *Node {
|
||||
func newDir(store *store, nodePath string, createdIndex uint64, parent *node,
|
||||
ACL string, expireTime time.Time) *node {
|
||||
|
||||
return &Node{
|
||||
return &node{
|
||||
Path: nodePath,
|
||||
CreateIndex: createIndex,
|
||||
ModifiedIndex: createIndex,
|
||||
CreatedIndex: createdIndex,
|
||||
ModifiedIndex: createdIndex,
|
||||
Parent: parent,
|
||||
ACL: ACL,
|
||||
ExpireTime: expireTime,
|
||||
Children: make(map[string]*Node),
|
||||
Children: make(map[string]*node),
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
@ -67,14 +67,14 @@ func newDir(store *store, nodePath string, createIndex uint64, parent *Node,
|
||||
// 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"
|
||||
// will only return /foo/notHidden
|
||||
func (n *Node) IsHidden() bool {
|
||||
func (n *node) IsHidden() bool {
|
||||
_, name := path.Split(n.Path)
|
||||
|
||||
return name[0] == '_'
|
||||
}
|
||||
|
||||
// IsPermanent function checks if the node is a permanent one.
|
||||
func (n *Node) IsPermanent() bool {
|
||||
func (n *node) IsPermanent() bool {
|
||||
// we use a uninitialized time.Time to indicate the node is a
|
||||
// permanent one.
|
||||
// the uninitialized time.Time should equal zero.
|
||||
@ -84,13 +84,13 @@ func (n *Node) IsPermanent() bool {
|
||||
// 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 {
|
||||
func (n *node) IsDir() bool {
|
||||
return !(n.Children == nil)
|
||||
}
|
||||
|
||||
// Read function gets the value of the node.
|
||||
// If the receiver node is not a key-value pair, a "Not A File" error will be returned.
|
||||
func (n *Node) Read() (string, *etcdErr.Error) {
|
||||
func (n *node) Read() (string, *etcdErr.Error) {
|
||||
if n.IsDir() {
|
||||
return "", etcdErr.NewError(etcdErr.EcodeNotFile, "", n.store.Index())
|
||||
}
|
||||
@ -100,7 +100,7 @@ func (n *Node) Read() (string, *etcdErr.Error) {
|
||||
|
||||
// Write function set the value of the node to the given value.
|
||||
// If the receiver node is a directory, a "Not A File" error will be returned.
|
||||
func (n *Node) Write(value string, index uint64) *etcdErr.Error {
|
||||
func (n *node) Write(value string, index uint64) *etcdErr.Error {
|
||||
if n.IsDir() {
|
||||
return etcdErr.NewError(etcdErr.EcodeNotFile, "", n.store.Index())
|
||||
}
|
||||
@ -111,7 +111,7 @@ func (n *Node) Write(value string, index uint64) *etcdErr.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) ExpirationAndTTL() (*time.Time, int64) {
|
||||
func (n *node) ExpirationAndTTL() (*time.Time, int64) {
|
||||
if !n.IsPermanent() {
|
||||
return &n.ExpireTime, int64(n.ExpireTime.Sub(time.Now())/time.Second) + 1
|
||||
}
|
||||
@ -120,12 +120,12 @@ func (n *Node) ExpirationAndTTL() (*time.Time, int64) {
|
||||
|
||||
// List function return a slice of nodes under the receiver node.
|
||||
// If the receiver node is not a directory, a "Not A Directory" error will be returned.
|
||||
func (n *Node) List() ([]*Node, *etcdErr.Error) {
|
||||
func (n *node) List() ([]*node, *etcdErr.Error) {
|
||||
if !n.IsDir() {
|
||||
return nil, etcdErr.NewError(etcdErr.EcodeNotDir, "", n.store.Index())
|
||||
}
|
||||
|
||||
nodes := make([]*Node, len(n.Children))
|
||||
nodes := make([]*node, len(n.Children))
|
||||
|
||||
i := 0
|
||||
for _, node := range n.Children {
|
||||
@ -138,7 +138,7 @@ func (n *Node) List() ([]*Node, *etcdErr.Error) {
|
||||
|
||||
// GetChild function returns the child node under the directory node.
|
||||
// On success, it returns the file node
|
||||
func (n *Node) GetChild(name string) (*Node, *etcdErr.Error) {
|
||||
func (n *node) GetChild(name string) (*node, *etcdErr.Error) {
|
||||
if !n.IsDir() {
|
||||
return nil, etcdErr.NewError(etcdErr.EcodeNotDir, n.Path, n.store.Index())
|
||||
}
|
||||
@ -156,7 +156,7 @@ func (n *Node) GetChild(name string) (*Node, *etcdErr.Error) {
|
||||
// If the receiver is not a directory, a "Not A Directory" error will be returned.
|
||||
// If there is a existing node with the same name under the directory, a "Already Exist"
|
||||
// error will be returned
|
||||
func (n *Node) Add(child *Node) *etcdErr.Error {
|
||||
func (n *node) Add(child *node) *etcdErr.Error {
|
||||
if !n.IsDir() {
|
||||
return etcdErr.NewError(etcdErr.EcodeNotDir, "", n.store.Index())
|
||||
}
|
||||
@ -175,7 +175,7 @@ func (n *Node) Add(child *Node) *etcdErr.Error {
|
||||
}
|
||||
|
||||
// Remove function remove the node.
|
||||
func (n *Node) Remove(recursive bool, callback func(path string)) *etcdErr.Error {
|
||||
func (n *node) Remove(recursive bool, callback func(path string)) *etcdErr.Error {
|
||||
|
||||
if n.IsDir() && !recursive {
|
||||
// cannot delete a directory without set recursive to true
|
||||
@ -223,21 +223,22 @@ func (n *Node) Remove(recursive bool, callback func(path string)) *etcdErr.Error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) Pair(recurisive, sorted bool) KeyValuePair {
|
||||
func (n *node) Repr(recurisive, sorted bool) NodeExtern {
|
||||
if n.IsDir() {
|
||||
pair := KeyValuePair{
|
||||
node := NodeExtern{
|
||||
Key: n.Path,
|
||||
Dir: true,
|
||||
ModifiedIndex: n.ModifiedIndex,
|
||||
CreatedIndex: n.CreatedIndex,
|
||||
}
|
||||
pair.Expiration, pair.TTL = n.ExpirationAndTTL()
|
||||
node.Expiration, node.TTL = n.ExpirationAndTTL()
|
||||
|
||||
if !recurisive {
|
||||
return pair
|
||||
return node
|
||||
}
|
||||
|
||||
children, _ := n.List()
|
||||
pair.KVPairs = make([]KeyValuePair, len(children))
|
||||
node.Nodes = make(NodeExterns, len(children))
|
||||
|
||||
// we do not use the index in the children slice directly
|
||||
// we need to skip the hidden one
|
||||
@ -249,30 +250,31 @@ func (n *Node) Pair(recurisive, sorted bool) KeyValuePair {
|
||||
continue
|
||||
}
|
||||
|
||||
pair.KVPairs[i] = child.Pair(recurisive, sorted)
|
||||
node.Nodes[i] = child.Repr(recurisive, sorted)
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
// eliminate hidden nodes
|
||||
pair.KVPairs = pair.KVPairs[:i]
|
||||
node.Nodes = node.Nodes[:i]
|
||||
if sorted {
|
||||
sort.Sort(pair.KVPairs)
|
||||
sort.Sort(node.Nodes)
|
||||
}
|
||||
|
||||
return pair
|
||||
return node
|
||||
}
|
||||
|
||||
pair := KeyValuePair{
|
||||
node := NodeExtern{
|
||||
Key: n.Path,
|
||||
Value: n.Value,
|
||||
ModifiedIndex: n.ModifiedIndex,
|
||||
CreatedIndex: n.CreatedIndex,
|
||||
}
|
||||
pair.Expiration, pair.TTL = n.ExpirationAndTTL()
|
||||
return pair
|
||||
node.Expiration, node.TTL = n.ExpirationAndTTL()
|
||||
return node
|
||||
}
|
||||
|
||||
func (n *Node) UpdateTTL(expireTime time.Time) {
|
||||
func (n *node) UpdateTTL(expireTime time.Time) {
|
||||
|
||||
if !n.IsPermanent() {
|
||||
if expireTime.IsZero() {
|
||||
@ -299,12 +301,12 @@ func (n *Node) UpdateTTL(expireTime time.Time) {
|
||||
// Clone function clone the node recursively and return the new node.
|
||||
// If the node is a directory, it will clone all the content under this directory.
|
||||
// If the node is a key-value pair, it will clone the pair.
|
||||
func (n *Node) Clone() *Node {
|
||||
func (n *node) Clone() *node {
|
||||
if !n.IsDir() {
|
||||
return newKV(n.store, n.Path, n.Value, n.CreateIndex, n.Parent, n.ACL, n.ExpireTime)
|
||||
return newKV(n.store, n.Path, n.Value, n.CreatedIndex, n.Parent, n.ACL, n.ExpireTime)
|
||||
}
|
||||
|
||||
clone := newDir(n.store, n.Path, n.CreateIndex, n.Parent, n.ACL, n.ExpireTime)
|
||||
clone := newDir(n.store, n.Path, n.CreatedIndex, n.Parent, n.ACL, n.ExpireTime)
|
||||
|
||||
for key, child := range n.Children {
|
||||
clone.Children[key] = child.Clone()
|
||||
@ -320,7 +322,7 @@ func (n *Node) Clone() *Node {
|
||||
// call this function on its children.
|
||||
// We check the expire last since we need to recover the whole structure first and add all the
|
||||
// notifications into the event history.
|
||||
func (n *Node) recoverAndclean() {
|
||||
func (n *node) recoverAndclean() {
|
||||
if n.IsDir() {
|
||||
for _, child := range n.Children {
|
||||
child.Parent = n
|
||||
|
36
store/node_extern.go
Normal file
36
store/node_extern.go
Normal file
@ -0,0 +1,36 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// NodeExtern is the external representation of the
|
||||
// internal node with additional fields
|
||||
// PrevValue is the previous value of the node
|
||||
// TTL is time to live in second
|
||||
type NodeExtern struct {
|
||||
Key string `json:"key, omitempty"`
|
||||
PrevValue string `json:"prevValue,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Dir bool `json:"dir,omitempty"`
|
||||
Expiration *time.Time `json:"expiration,omitempty"`
|
||||
TTL int64 `json:"ttl,omitempty"`
|
||||
Nodes NodeExterns `json:"nodes,omitempty"`
|
||||
ModifiedIndex uint64 `json:"modifiedIndex,omitempty"`
|
||||
CreatedIndex uint64 `json:"createdIndex,omitempty"`
|
||||
}
|
||||
|
||||
type NodeExterns []NodeExtern
|
||||
|
||||
// interfaces for sorting
|
||||
func (ns NodeExterns) Len() int {
|
||||
return len(ns)
|
||||
}
|
||||
|
||||
func (ns NodeExterns) Less(i, j int) bool {
|
||||
return ns[i].Key < ns[j].Key
|
||||
}
|
||||
|
||||
func (ns NodeExterns) Swap(i, j int) {
|
||||
ns[i], ns[j] = ns[j], ns[i]
|
||||
}
|
@ -59,7 +59,7 @@ type Store interface {
|
||||
}
|
||||
|
||||
type store struct {
|
||||
Root *Node
|
||||
Root *node
|
||||
WatcherHub *watcherHub
|
||||
CurrentIndex uint64
|
||||
Stats *Stats
|
||||
@ -113,13 +113,14 @@ func (s *store) Get(nodePath string, recursive, sorted bool) (*Event, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e := newEvent(Get, nodePath, n.ModifiedIndex)
|
||||
e := newEvent(Get, nodePath, n.ModifiedIndex, n.CreatedIndex)
|
||||
eNode := e.Node
|
||||
|
||||
if n.IsDir() { // node is a directory
|
||||
e.Dir = true
|
||||
eNode.Dir = true
|
||||
|
||||
children, _ := n.List()
|
||||
e.KVPairs = make([]KeyValuePair, len(children))
|
||||
eNode.Nodes = make(NodeExterns, len(children))
|
||||
|
||||
// we do not use the index in the children slice directly
|
||||
// we need to skip the hidden one
|
||||
@ -130,29 +131,29 @@ func (s *store) Get(nodePath string, recursive, sorted bool) (*Event, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
e.KVPairs[i] = child.Pair(recursive, sorted)
|
||||
eNode.Nodes[i] = child.Repr(recursive, sorted)
|
||||
i++
|
||||
}
|
||||
|
||||
// eliminate hidden nodes
|
||||
e.KVPairs = e.KVPairs[:i]
|
||||
eNode.Nodes = eNode.Nodes[:i]
|
||||
|
||||
if sorted {
|
||||
sort.Sort(e.KVPairs)
|
||||
sort.Sort(eNode.Nodes)
|
||||
}
|
||||
|
||||
} else { // node is a file
|
||||
e.Value, _ = n.Read()
|
||||
eNode.Value, _ = n.Read()
|
||||
}
|
||||
|
||||
e.Expiration, e.TTL = n.ExpirationAndTTL()
|
||||
eNode.Expiration, eNode.TTL = n.ExpirationAndTTL()
|
||||
|
||||
s.Stats.Inc(GetSuccess)
|
||||
|
||||
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 any node on the path is a file, create will fail.
|
||||
func (s *store) Create(nodePath string, value string, unique bool, expireTime time.Time) (*Event, error) {
|
||||
@ -169,7 +170,7 @@ func (s *store) Create(nodePath string, value string, unique bool, expireTime ti
|
||||
return e, err
|
||||
}
|
||||
|
||||
// Set function creates or replace the Node at nodePath.
|
||||
// Set function creates or replace the node at nodePath.
|
||||
func (s *store) Set(nodePath string, value string, expireTime time.Time) (*Event, error) {
|
||||
s.worldLock.Lock()
|
||||
defer s.worldLock.Unlock()
|
||||
@ -214,15 +215,17 @@ func (s *store) CompareAndSwap(nodePath string, prevValue string, prevIndex uint
|
||||
// update etcd index
|
||||
s.CurrentIndex++
|
||||
|
||||
e := newEvent(CompareAndSwap, nodePath, s.CurrentIndex)
|
||||
e.PrevValue = n.Value
|
||||
e := newEvent(CompareAndSwap, nodePath, s.CurrentIndex, n.CreatedIndex)
|
||||
eNode := e.Node
|
||||
|
||||
eNode.PrevValue = n.Value
|
||||
|
||||
// if test succeed, write the value
|
||||
n.Write(value, s.CurrentIndex)
|
||||
n.UpdateTTL(expireTime)
|
||||
|
||||
e.Value = value
|
||||
e.Expiration, e.TTL = n.ExpirationAndTTL()
|
||||
eNode.Value = value
|
||||
eNode.Expiration, eNode.TTL = n.ExpirationAndTTL()
|
||||
|
||||
s.WatcherHub.notify(e)
|
||||
s.Stats.Inc(CompareAndSwapSuccess)
|
||||
@ -255,12 +258,13 @@ func (s *store) Delete(nodePath string, recursive bool) (*Event, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e := newEvent(Delete, nodePath, nextIndex)
|
||||
e := newEvent(Delete, nodePath, nextIndex, n.CreatedIndex)
|
||||
eNode := e.Node
|
||||
|
||||
if n.IsDir() {
|
||||
e.Dir = true
|
||||
eNode.Dir = true
|
||||
} else {
|
||||
e.PrevValue = n.Value
|
||||
eNode.PrevValue = n.Value
|
||||
}
|
||||
|
||||
callback := func(path string) { // notify function
|
||||
@ -313,7 +317,7 @@ func (s *store) Watch(key string, recursive bool, sinceIndex uint64) (<-chan *Ev
|
||||
}
|
||||
|
||||
// walk function walks all the nodePath and apply the walkFunc on each directory
|
||||
func (s *store) walk(nodePath string, walkFunc func(prev *Node, component string) (*Node, *etcdErr.Error)) (*Node, *etcdErr.Error) {
|
||||
func (s *store) walk(nodePath string, walkFunc func(prev *node, component string) (*node, *etcdErr.Error)) (*node, *etcdErr.Error) {
|
||||
components := strings.Split(nodePath, "/")
|
||||
|
||||
curr := s.Root
|
||||
@ -356,7 +360,8 @@ func (s *store) Update(nodePath string, newValue string, expireTime time.Time) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e := newEvent(Update, nodePath, nextIndex)
|
||||
e := newEvent(Update, nodePath, nextIndex, n.CreatedIndex)
|
||||
eNode := e.Node
|
||||
|
||||
if len(newValue) != 0 {
|
||||
if n.IsDir() {
|
||||
@ -365,18 +370,19 @@ func (s *store) Update(nodePath string, newValue string, expireTime time.Time) (
|
||||
return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, currIndex)
|
||||
}
|
||||
|
||||
e.PrevValue = n.Value
|
||||
eNode.PrevValue = n.Value
|
||||
n.Write(newValue, nextIndex)
|
||||
e.Value = newValue
|
||||
eNode.Value = newValue
|
||||
|
||||
} else {
|
||||
// do not update value
|
||||
e.Value = n.Value
|
||||
eNode.Value = n.Value
|
||||
}
|
||||
|
||||
// update ttl
|
||||
n.UpdateTTL(expireTime)
|
||||
|
||||
e.Expiration, e.TTL = n.ExpirationAndTTL()
|
||||
eNode.Expiration, eNode.TTL = n.ExpirationAndTTL()
|
||||
|
||||
s.WatcherHub.notify(e)
|
||||
|
||||
@ -420,7 +426,8 @@ func (s *store) internalCreate(nodePath string, value string, unique bool, repla
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e := newEvent(action, nodePath, nextIndex)
|
||||
e := newEvent(action, nodePath, nextIndex, nextIndex)
|
||||
eNode := e.Node
|
||||
|
||||
n, _ := d.GetChild(newNodeName)
|
||||
|
||||
@ -430,7 +437,7 @@ func (s *store) internalCreate(nodePath string, value string, unique bool, repla
|
||||
if n.IsDir() {
|
||||
return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, currIndex)
|
||||
}
|
||||
e.PrevValue, _ = n.Read()
|
||||
eNode.PrevValue, _ = n.Read()
|
||||
|
||||
n.Remove(false, nil)
|
||||
} else {
|
||||
@ -439,12 +446,12 @@ func (s *store) internalCreate(nodePath string, value string, unique bool, repla
|
||||
}
|
||||
|
||||
if len(value) != 0 { // create file
|
||||
e.Value = value
|
||||
eNode.Value = value
|
||||
|
||||
n = newKV(s, nodePath, value, nextIndex, d, "", expireTime)
|
||||
|
||||
} else { // create directory
|
||||
e.Dir = true
|
||||
eNode.Dir = true
|
||||
|
||||
n = newDir(s, nodePath, nextIndex, d, "", expireTime)
|
||||
|
||||
@ -453,11 +460,11 @@ func (s *store) internalCreate(nodePath string, value string, unique bool, repla
|
||||
// we are sure d is a directory and does not have the children with name n.Name
|
||||
d.Add(n)
|
||||
|
||||
// Node with TTL
|
||||
// node with TTL
|
||||
if !n.IsPermanent() {
|
||||
s.ttlKeyHeap.push(n)
|
||||
|
||||
e.Expiration, e.TTL = n.ExpirationAndTTL()
|
||||
eNode.Expiration, eNode.TTL = n.ExpirationAndTTL()
|
||||
}
|
||||
|
||||
s.CurrentIndex = nextIndex
|
||||
@ -467,10 +474,10 @@ func (s *store) internalCreate(nodePath string, value string, unique bool, repla
|
||||
}
|
||||
|
||||
// InternalGet function get the node of the given nodePath.
|
||||
func (s *store) internalGet(nodePath string) (*Node, *etcdErr.Error) {
|
||||
func (s *store) internalGet(nodePath string) (*node, *etcdErr.Error) {
|
||||
nodePath = path.Clean(path.Join("/", nodePath))
|
||||
|
||||
walkFunc := func(parent *Node, name string) (*Node, *etcdErr.Error) {
|
||||
walkFunc := func(parent *node, name string) (*node, *etcdErr.Error) {
|
||||
|
||||
if !parent.IsDir() {
|
||||
err := etcdErr.NewError(etcdErr.EcodeNotDir, parent.Path, s.CurrentIndex)
|
||||
@ -510,7 +517,7 @@ func (s *store) DeleteExpiredKeys(cutoff time.Time) {
|
||||
s.CurrentIndex++
|
||||
|
||||
s.Stats.Inc(ExpireCount)
|
||||
s.WatcherHub.notify(newEvent(Expire, node.Path, s.CurrentIndex))
|
||||
s.WatcherHub.notify(newEvent(Expire, node.Path, s.CurrentIndex, node.CreatedIndex))
|
||||
}
|
||||
|
||||
}
|
||||
@ -519,7 +526,7 @@ func (s *store) DeleteExpiredKeys(cutoff time.Time) {
|
||||
// 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 is a file, this function will return error.
|
||||
func (s *store) checkDir(parent *Node, dirName string) (*Node, *etcdErr.Error) {
|
||||
func (s *store) checkDir(parent *node, dirName string) (*node, *etcdErr.Error) {
|
||||
node, ok := parent.Children[dirName]
|
||||
|
||||
if ok {
|
||||
|
@ -31,8 +31,8 @@ func TestStoreGetValue(t *testing.T) {
|
||||
e, err := s.Get("/foo", false, false)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, e.Action, "get", "")
|
||||
assert.Equal(t, e.Key, "/foo", "")
|
||||
assert.Equal(t, e.Value, "bar", "")
|
||||
assert.Equal(t, e.Node.Key, "/foo", "")
|
||||
assert.Equal(t, e.Node.Value, "bar", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can recrusively retrieve a directory listing.
|
||||
@ -49,21 +49,21 @@ func TestStoreGetDirectory(t *testing.T) {
|
||||
e, err := s.Get("/foo", true, false)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, e.Action, "get", "")
|
||||
assert.Equal(t, e.Key, "/foo", "")
|
||||
assert.Equal(t, len(e.KVPairs), 2, "")
|
||||
assert.Equal(t, e.KVPairs[0].Key, "/foo/bar", "")
|
||||
assert.Equal(t, e.KVPairs[0].Value, "X", "")
|
||||
assert.Equal(t, e.KVPairs[0].Dir, false, "")
|
||||
assert.Equal(t, e.KVPairs[1].Key, "/foo/baz", "")
|
||||
assert.Equal(t, e.KVPairs[1].Dir, true, "")
|
||||
assert.Equal(t, len(e.KVPairs[1].KVPairs), 2, "")
|
||||
assert.Equal(t, e.KVPairs[1].KVPairs[0].Key, "/foo/baz/bat", "")
|
||||
assert.Equal(t, e.KVPairs[1].KVPairs[0].Value, "Y", "")
|
||||
assert.Equal(t, e.KVPairs[1].KVPairs[0].Dir, false, "")
|
||||
assert.Equal(t, e.KVPairs[1].KVPairs[1].Key, "/foo/baz/ttl", "")
|
||||
assert.Equal(t, e.KVPairs[1].KVPairs[1].Value, "Y", "")
|
||||
assert.Equal(t, e.KVPairs[1].KVPairs[1].Dir, false, "")
|
||||
assert.Equal(t, e.KVPairs[1].KVPairs[1].TTL, 3, "")
|
||||
assert.Equal(t, e.Node.Key, "/foo", "")
|
||||
assert.Equal(t, len(e.Node.Nodes), 2, "")
|
||||
assert.Equal(t, e.Node.Nodes[0].Key, "/foo/bar", "")
|
||||
assert.Equal(t, e.Node.Nodes[0].Value, "X", "")
|
||||
assert.Equal(t, e.Node.Nodes[0].Dir, false, "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Key, "/foo/baz", "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Dir, true, "")
|
||||
assert.Equal(t, len(e.Node.Nodes[1].Nodes), 2, "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Nodes[0].Key, "/foo/baz/bat", "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Nodes[0].Value, "Y", "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Nodes[0].Dir, false, "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Nodes[1].Key, "/foo/baz/ttl", "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Nodes[1].Value, "Y", "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Nodes[1].Dir, false, "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Nodes[1].TTL, 3, "")
|
||||
}
|
||||
|
||||
// Ensure that the store can retrieve a directory in sorted order.
|
||||
@ -77,11 +77,11 @@ func TestStoreGetSorted(t *testing.T) {
|
||||
s.Create("/foo/y/b", "0", false, Permanent)
|
||||
e, err := s.Get("/foo", true, true)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, e.KVPairs[0].Key, "/foo/x", "")
|
||||
assert.Equal(t, e.KVPairs[1].Key, "/foo/y", "")
|
||||
assert.Equal(t, e.KVPairs[1].KVPairs[0].Key, "/foo/y/a", "")
|
||||
assert.Equal(t, e.KVPairs[1].KVPairs[1].Key, "/foo/y/b", "")
|
||||
assert.Equal(t, e.KVPairs[2].Key, "/foo/z", "")
|
||||
assert.Equal(t, e.Node.Nodes[0].Key, "/foo/x", "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Key, "/foo/y", "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Nodes[0].Key, "/foo/y/a", "")
|
||||
assert.Equal(t, e.Node.Nodes[1].Nodes[1].Key, "/foo/y/b", "")
|
||||
assert.Equal(t, e.Node.Nodes[2].Key, "/foo/z", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can create a new key if it doesn't already exist.
|
||||
@ -90,14 +90,14 @@ func TestStoreCreateValue(t *testing.T) {
|
||||
e, err := s.Create("/foo", "bar", false, Permanent)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, e.Action, "create", "")
|
||||
assert.Equal(t, e.Key, "/foo", "")
|
||||
assert.False(t, e.Dir, "")
|
||||
assert.Equal(t, e.PrevValue, "", "")
|
||||
assert.Equal(t, e.Value, "bar", "")
|
||||
assert.Nil(t, e.KVPairs, "")
|
||||
assert.Nil(t, e.Expiration, "")
|
||||
assert.Equal(t, e.TTL, 0, "")
|
||||
assert.Equal(t, e.ModifiedIndex, uint64(1), "")
|
||||
assert.Equal(t, e.Node.Key, "/foo", "")
|
||||
assert.False(t, e.Node.Dir, "")
|
||||
assert.Equal(t, e.Node.PrevValue, "", "")
|
||||
assert.Equal(t, e.Node.Value, "bar", "")
|
||||
assert.Nil(t, e.Node.Nodes, "")
|
||||
assert.Nil(t, e.Node.Expiration, "")
|
||||
assert.Equal(t, e.Node.TTL, 0, "")
|
||||
assert.Equal(t, e.Node.ModifiedIndex, uint64(1), "")
|
||||
}
|
||||
|
||||
// Ensure that the store can create a new directory if it doesn't already exist.
|
||||
@ -106,8 +106,8 @@ func TestStoreCreateDirectory(t *testing.T) {
|
||||
e, err := s.Create("/foo", "", false, Permanent)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, e.Action, "create", "")
|
||||
assert.Equal(t, e.Key, "/foo", "")
|
||||
assert.True(t, e.Dir, "")
|
||||
assert.Equal(t, e.Node.Key, "/foo", "")
|
||||
assert.True(t, e.Node.Dir, "")
|
||||
}
|
||||
|
||||
// Ensure that the store fails to create a key if it already exists.
|
||||
@ -130,14 +130,14 @@ func TestStoreUpdateValue(t *testing.T) {
|
||||
e, err := s.Update("/foo", "baz", Permanent)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, e.Action, "update", "")
|
||||
assert.Equal(t, e.Key, "/foo", "")
|
||||
assert.False(t, e.Dir, "")
|
||||
assert.Equal(t, e.PrevValue, "bar", "")
|
||||
assert.Equal(t, e.Value, "baz", "")
|
||||
assert.Equal(t, e.TTL, 0, "")
|
||||
assert.Equal(t, e.ModifiedIndex, uint64(2), "")
|
||||
assert.Equal(t, e.Node.Key, "/foo", "")
|
||||
assert.False(t, e.Node.Dir, "")
|
||||
assert.Equal(t, e.Node.PrevValue, "bar", "")
|
||||
assert.Equal(t, e.Node.Value, "baz", "")
|
||||
assert.Equal(t, e.Node.TTL, 0, "")
|
||||
assert.Equal(t, e.Node.ModifiedIndex, uint64(2), "")
|
||||
e, _ = s.Get("/foo", false, false)
|
||||
assert.Equal(t, e.Value, "baz", "")
|
||||
assert.Equal(t, e.Node.Value, "baz", "")
|
||||
}
|
||||
|
||||
// Ensure that the store cannot update a directory.
|
||||
@ -165,7 +165,7 @@ func TestStoreUpdateValueTTL(t *testing.T) {
|
||||
s.Create("/foo", "bar", false, Permanent)
|
||||
_, err := s.Update("/foo", "baz", time.Now().Add(500*time.Millisecond))
|
||||
e, _ := s.Get("/foo", false, false)
|
||||
assert.Equal(t, e.Value, "baz", "")
|
||||
assert.Equal(t, e.Node.Value, "baz", "")
|
||||
|
||||
time.Sleep(600 * time.Millisecond)
|
||||
e, err = s.Get("/foo", false, false)
|
||||
@ -187,7 +187,7 @@ func TestStoreUpdateDirTTL(t *testing.T) {
|
||||
s.Create("/foo/bar", "baz", false, Permanent)
|
||||
_, err := s.Update("/foo", "", time.Now().Add(500*time.Millisecond))
|
||||
e, _ := s.Get("/foo/bar", false, false)
|
||||
assert.Equal(t, e.Value, "baz", "")
|
||||
assert.Equal(t, e.Node.Value, "baz", "")
|
||||
|
||||
time.Sleep(600 * time.Millisecond)
|
||||
e, err = s.Get("/foo/bar", false, false)
|
||||
@ -251,10 +251,10 @@ func TestStoreCompareAndSwapPrevValue(t *testing.T) {
|
||||
e, err := s.CompareAndSwap("/foo", "bar", 0, "baz", Permanent)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, e.Action, "compareAndSwap", "")
|
||||
assert.Equal(t, e.PrevValue, "bar", "")
|
||||
assert.Equal(t, e.Value, "baz", "")
|
||||
assert.Equal(t, e.Node.PrevValue, "bar", "")
|
||||
assert.Equal(t, e.Node.Value, "baz", "")
|
||||
e, _ = s.Get("/foo", false, false)
|
||||
assert.Equal(t, e.Value, "baz", "")
|
||||
assert.Equal(t, e.Node.Value, "baz", "")
|
||||
}
|
||||
|
||||
// Ensure that the store cannot conditionally update a key if it has the wrong previous value.
|
||||
@ -267,7 +267,7 @@ func TestStoreCompareAndSwapPrevValueFailsIfNotMatch(t *testing.T) {
|
||||
assert.Equal(t, err.Message, "Test Failed", "")
|
||||
assert.Nil(t, e, "")
|
||||
e, _ = s.Get("/foo", false, false)
|
||||
assert.Equal(t, e.Value, "bar", "")
|
||||
assert.Equal(t, e.Node.Value, "bar", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can conditionally update a key if it has a previous index.
|
||||
@ -277,10 +277,10 @@ func TestStoreCompareAndSwapPrevIndex(t *testing.T) {
|
||||
e, err := s.CompareAndSwap("/foo", "", 1, "baz", Permanent)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, e.Action, "compareAndSwap", "")
|
||||
assert.Equal(t, e.PrevValue, "bar", "")
|
||||
assert.Equal(t, e.Value, "baz", "")
|
||||
assert.Equal(t, e.Node.PrevValue, "bar", "")
|
||||
assert.Equal(t, e.Node.Value, "baz", "")
|
||||
e, _ = s.Get("/foo", false, false)
|
||||
assert.Equal(t, e.Value, "baz", "")
|
||||
assert.Equal(t, e.Node.Value, "baz", "")
|
||||
}
|
||||
|
||||
// Ensure that the store cannot conditionally update a key if it has the wrong previous index.
|
||||
@ -293,7 +293,7 @@ func TestStoreCompareAndSwapPrevIndexFailsIfNotMatch(t *testing.T) {
|
||||
assert.Equal(t, err.Message, "Test Failed", "")
|
||||
assert.Nil(t, e, "")
|
||||
e, _ = s.Get("/foo", false, false)
|
||||
assert.Equal(t, e.Value, "bar", "")
|
||||
assert.Equal(t, e.Node.Value, "bar", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can watch for key creation.
|
||||
@ -303,7 +303,7 @@ func TestStoreWatchCreate(t *testing.T) {
|
||||
s.Create("/foo", "bar", false, Permanent)
|
||||
e := nbselect(c)
|
||||
assert.Equal(t, e.Action, "create", "")
|
||||
assert.Equal(t, e.Key, "/foo", "")
|
||||
assert.Equal(t, e.Node.Key, "/foo", "")
|
||||
e = nbselect(c)
|
||||
assert.Nil(t, e, "")
|
||||
}
|
||||
@ -315,7 +315,7 @@ func TestStoreWatchRecursiveCreate(t *testing.T) {
|
||||
s.Create("/foo/bar", "baz", false, Permanent)
|
||||
e := nbselect(c)
|
||||
assert.Equal(t, e.Action, "create", "")
|
||||
assert.Equal(t, e.Key, "/foo/bar", "")
|
||||
assert.Equal(t, e.Node.Key, "/foo/bar", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can watch for key updates.
|
||||
@ -326,7 +326,7 @@ func TestStoreWatchUpdate(t *testing.T) {
|
||||
s.Update("/foo", "baz", Permanent)
|
||||
e := nbselect(c)
|
||||
assert.Equal(t, e.Action, "update", "")
|
||||
assert.Equal(t, e.Key, "/foo", "")
|
||||
assert.Equal(t, e.Node.Key, "/foo", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can watch for recursive key updates.
|
||||
@ -337,7 +337,7 @@ func TestStoreWatchRecursiveUpdate(t *testing.T) {
|
||||
s.Update("/foo/bar", "baz", Permanent)
|
||||
e := nbselect(c)
|
||||
assert.Equal(t, e.Action, "update", "")
|
||||
assert.Equal(t, e.Key, "/foo/bar", "")
|
||||
assert.Equal(t, e.Node.Key, "/foo/bar", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can watch for key deletions.
|
||||
@ -348,7 +348,7 @@ func TestStoreWatchDelete(t *testing.T) {
|
||||
s.Delete("/foo", false)
|
||||
e := nbselect(c)
|
||||
assert.Equal(t, e.Action, "delete", "")
|
||||
assert.Equal(t, e.Key, "/foo", "")
|
||||
assert.Equal(t, e.Node.Key, "/foo", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can watch for recursive key deletions.
|
||||
@ -359,7 +359,7 @@ func TestStoreWatchRecursiveDelete(t *testing.T) {
|
||||
s.Delete("/foo/bar", false)
|
||||
e := nbselect(c)
|
||||
assert.Equal(t, e.Action, "delete", "")
|
||||
assert.Equal(t, e.Key, "/foo/bar", "")
|
||||
assert.Equal(t, e.Node.Key, "/foo/bar", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can watch for CAS updates.
|
||||
@ -370,7 +370,7 @@ func TestStoreWatchCompareAndSwap(t *testing.T) {
|
||||
s.CompareAndSwap("/foo", "bar", 0, "baz", Permanent)
|
||||
e := nbselect(c)
|
||||
assert.Equal(t, e.Action, "compareAndSwap", "")
|
||||
assert.Equal(t, e.Key, "/foo", "")
|
||||
assert.Equal(t, e.Node.Key, "/foo", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can watch for recursive CAS updates.
|
||||
@ -381,7 +381,7 @@ func TestStoreWatchRecursiveCompareAndSwap(t *testing.T) {
|
||||
s.CompareAndSwap("/foo/bar", "baz", 0, "bat", Permanent)
|
||||
e := nbselect(c)
|
||||
assert.Equal(t, e.Action, "compareAndSwap", "")
|
||||
assert.Equal(t, e.Key, "/foo/bar", "")
|
||||
assert.Equal(t, e.Node.Key, "/foo/bar", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can watch for key expiration.
|
||||
@ -403,11 +403,11 @@ func TestStoreWatchExpire(t *testing.T) {
|
||||
time.Sleep(600 * time.Millisecond)
|
||||
e = nbselect(c)
|
||||
assert.Equal(t, e.Action, "expire", "")
|
||||
assert.Equal(t, e.Key, "/foo", "")
|
||||
assert.Equal(t, e.Node.Key, "/foo", "")
|
||||
c, _ = s.Watch("/", true, 4)
|
||||
e = nbselect(c)
|
||||
assert.Equal(t, e.Action, "expire", "")
|
||||
assert.Equal(t, e.Key, "/foofoo", "")
|
||||
assert.Equal(t, e.Node.Key, "/foofoo", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can recover from a previously saved state.
|
||||
@ -423,11 +423,11 @@ func TestStoreRecover(t *testing.T) {
|
||||
|
||||
e, err := s.Get("/foo/x", false, false)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, e.Value, "bar", "")
|
||||
assert.Equal(t, e.Node.Value, "bar", "")
|
||||
|
||||
e, err = s.Get("/foo/y", false, false)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, e.Value, "baz", "")
|
||||
assert.Equal(t, e.Node.Value, "baz", "")
|
||||
}
|
||||
|
||||
// Ensure that the store can recover from a previously saved state that includes an expiring key.
|
||||
@ -461,7 +461,7 @@ func TestStoreRecoverWithExpiration(t *testing.T) {
|
||||
|
||||
e, err := s.Get("/foo/x", false, false)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, e.Value, "bar", "")
|
||||
assert.Equal(t, e.Node.Value, "bar", "")
|
||||
|
||||
e, err = s.Get("/foo/y", false, false)
|
||||
assert.NotNil(t, err, "")
|
||||
|
@ -6,12 +6,12 @@ import (
|
||||
|
||||
// An TTLKeyHeap is a min-heap of TTLKeys order by expiration time
|
||||
type ttlKeyHeap struct {
|
||||
array []*Node
|
||||
keyMap map[*Node]int
|
||||
array []*node
|
||||
keyMap map[*node]int
|
||||
}
|
||||
|
||||
func newTtlKeyHeap() *ttlKeyHeap {
|
||||
h := &ttlKeyHeap{keyMap: make(map[*Node]int)}
|
||||
h := &ttlKeyHeap{keyMap: make(map[*node]int)}
|
||||
heap.Init(h)
|
||||
return h
|
||||
}
|
||||
@ -34,7 +34,7 @@ func (h ttlKeyHeap) Swap(i, j int) {
|
||||
}
|
||||
|
||||
func (h *ttlKeyHeap) Push(x interface{}) {
|
||||
n, _ := x.(*Node)
|
||||
n, _ := x.(*node)
|
||||
h.keyMap[n] = len(h.array)
|
||||
h.array = append(h.array, n)
|
||||
}
|
||||
@ -48,16 +48,16 @@ func (h *ttlKeyHeap) Pop() interface{} {
|
||||
return x
|
||||
}
|
||||
|
||||
func (h *ttlKeyHeap) top() *Node {
|
||||
func (h *ttlKeyHeap) top() *node {
|
||||
if h.Len() != 0 {
|
||||
return h.array[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ttlKeyHeap) pop() *Node {
|
||||
func (h *ttlKeyHeap) pop() *node {
|
||||
x := heap.Pop(h)
|
||||
n, _ := x.(*Node)
|
||||
n, _ := x.(*node)
|
||||
return n
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ func (h *ttlKeyHeap) push(x interface{}) {
|
||||
heap.Push(h, x)
|
||||
}
|
||||
|
||||
func (h *ttlKeyHeap) update(n *Node) {
|
||||
func (h *ttlKeyHeap) update(n *node) {
|
||||
index, ok := h.keyMap[n]
|
||||
if ok {
|
||||
heap.Remove(h, index)
|
||||
@ -73,7 +73,7 @@ func (h *ttlKeyHeap) update(n *Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ttlKeyHeap) remove(n *Node) {
|
||||
func (h *ttlKeyHeap) remove(n *node) {
|
||||
index, ok := h.keyMap[n]
|
||||
if ok {
|
||||
heap.Remove(h, index)
|
||||
|
@ -77,7 +77,7 @@ func (wh *watcherHub) watch(key string, recursive bool, index uint64) (<-chan *E
|
||||
func (wh *watcherHub) notify(e *Event) {
|
||||
e = wh.EventHistory.addEvent(e) // add event into the eventHistory
|
||||
|
||||
segments := strings.Split(e.Key, "/")
|
||||
segments := strings.Split(e.Node.Key, "/")
|
||||
|
||||
currPath := "/"
|
||||
|
||||
@ -111,7 +111,7 @@ func (wh *watcherHub) notifyWatchers(e *Event, path string, deleted bool) {
|
||||
|
||||
w, _ := curr.Value.(*watcher)
|
||||
|
||||
if w.notify(e, e.Key == path, deleted) {
|
||||
if w.notify(e, e.Node.Key == path, deleted) {
|
||||
|
||||
// if we successfully notify a watcher
|
||||
// we need to remove the watcher from the list
|
||||
|
@ -35,7 +35,7 @@ func TestWatcher(t *testing.T) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
e := newEvent(Create, "/foo/bar", 1)
|
||||
e := newEvent(Create, "/foo/bar", 1, 1)
|
||||
|
||||
wh.notify(e)
|
||||
|
||||
@ -47,7 +47,7 @@ func TestWatcher(t *testing.T) {
|
||||
|
||||
c, _ = wh.watch("/foo", false, 2)
|
||||
|
||||
e = newEvent(Create, "/foo/bar", 2)
|
||||
e = newEvent(Create, "/foo/bar", 2, 2)
|
||||
|
||||
wh.notify(e)
|
||||
|
||||
@ -58,7 +58,7 @@ func TestWatcher(t *testing.T) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
e = newEvent(Create, "/foo", 3)
|
||||
e = newEvent(Create, "/foo", 3, 3)
|
||||
|
||||
wh.notify(e)
|
||||
|
||||
@ -78,7 +78,7 @@ func TestWatcher(t *testing.T) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
e = newEvent(Create, "/fo/bar", 3)
|
||||
e = newEvent(Create, "/fo/bar", 3, 3)
|
||||
|
||||
wh.notify(e)
|
||||
|
||||
|
@ -65,7 +65,7 @@ func TestMultiNodeKillAllAndRecovery(t *testing.T) {
|
||||
t.Fatalf("Recovery error: %s", err)
|
||||
}
|
||||
|
||||
if result.ModifiedIndex != 16 {
|
||||
t.Fatalf("recovery failed! [%d/16]", result.ModifiedIndex)
|
||||
if result.Node.ModifiedIndex != 16 {
|
||||
t.Fatalf("recovery failed! [%d/16]", result.Node.ModifiedIndex)
|
||||
}
|
||||
}
|
||||
|
@ -35,13 +35,13 @@ func TestRemoveNode(t *testing.T) {
|
||||
fmt.Println("send remove to node3 and wait for its exiting")
|
||||
etcds[2].Wait()
|
||||
|
||||
resp, err := c.Get("_etcd/machines", false)
|
||||
resp, err := c.Get("_etcd/machines", false, false)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(resp.Kvs) != 2 {
|
||||
if len(resp.Node.Nodes) != 2 {
|
||||
t.Fatal("cannot remove peer")
|
||||
}
|
||||
|
||||
@ -59,14 +59,14 @@ func TestRemoveNode(t *testing.T) {
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
resp, err = c.Get("_etcd/machines", false)
|
||||
resp, err = c.Get("_etcd/machines", false, false)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(resp.Kvs) != 3 {
|
||||
t.Fatalf("add peer fails #1 (%d != 3)", len(resp.Kvs))
|
||||
if len(resp.Node.Nodes) != 3 {
|
||||
t.Fatalf("add peer fails #1 (%d != 3)", len(resp.Node.Nodes))
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,13 +78,13 @@ func TestRemoveNode(t *testing.T) {
|
||||
|
||||
client.Do(rmReq)
|
||||
|
||||
resp, err := c.Get("_etcd/machines", false)
|
||||
resp, err := c.Get("_etcd/machines", false, false)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(resp.Kvs) != 2 {
|
||||
if len(resp.Node.Nodes) != 2 {
|
||||
t.Fatal("cannot remove peer")
|
||||
}
|
||||
|
||||
@ -102,14 +102,14 @@ func TestRemoveNode(t *testing.T) {
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
resp, err = c.Get("_etcd/machines", false)
|
||||
resp, err = c.Get("_etcd/machines", false, false)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(resp.Kvs) != 3 {
|
||||
t.Fatalf("add peer fails #2 (%d != 3)", len(resp.Kvs))
|
||||
if len(resp.Node.Nodes) != 3 {
|
||||
t.Fatalf("add peer fails #2 (%d != 3)", len(resp.Node.Nodes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,24 +39,26 @@ func templateTestSimpleMultiNode(t *testing.T, tls bool) {
|
||||
|
||||
// Test Set
|
||||
result, err := c.Set("foo", "bar", 100)
|
||||
node := result.Node
|
||||
|
||||
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 {
|
||||
if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Fatalf("Set 1 failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
t.Fatalf("Set 1 failed with %s %s %v", node.Key, node.Value, node.TTL)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
result, err = c.Set("foo", "bar", 100)
|
||||
node = result.Node
|
||||
|
||||
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.PrevValue != "bar" || result.TTL < 95 {
|
||||
if err != nil || node.Key != "/foo" || node.Value != "bar" || node.PrevValue != "bar" || node.TTL < 95 {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Fatalf("Set 2 failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
t.Fatalf("Set 2 failed with %s %s %v", node.Key, node.Value, node.TTL)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,13 +30,14 @@ func TestSimpleSnapshot(t *testing.T) {
|
||||
// issue first 501 commands
|
||||
for i := 0; i < 501; i++ {
|
||||
result, err := c.Set("foo", "bar", 100)
|
||||
node := result.Node
|
||||
|
||||
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 {
|
||||
if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Fatalf("Set failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
t.Fatalf("Set failed with %s %s %v", node.Key, node.Value, node.TTL)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,13 +63,14 @@ func TestSimpleSnapshot(t *testing.T) {
|
||||
// issue second 501 commands
|
||||
for i := 0; i < 501; i++ {
|
||||
result, err := c.Set("foo", "bar", 100)
|
||||
node := result.Node
|
||||
|
||||
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 {
|
||||
if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Fatalf("Set failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
t.Fatalf("Set failed with %s %s %v", node.Key, node.Value, node.TTL)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,13 +28,14 @@ func TestSingleNodeRecovery(t *testing.T) {
|
||||
c.SyncCluster()
|
||||
// Test Set
|
||||
result, err := c.Set("foo", "bar", 100)
|
||||
node := result.Node
|
||||
|
||||
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 {
|
||||
if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Fatalf("Set 1 failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
t.Fatalf("Set 1 failed with %s %s %v", node.Key, node.Value, node.TTL)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
@ -50,16 +51,18 @@ func TestSingleNodeRecovery(t *testing.T) {
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
result, err = c.Get("foo", false)
|
||||
result, err = c.Get("foo", false, false)
|
||||
node = result.Node
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("get fail: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL > 99 {
|
||||
if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL > 99 {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Fatalf("Recovery Get failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
t.Fatalf("Recovery Get failed with %s %s %v", node.Key, node.Value, node.TTL)
|
||||
}
|
||||
}
|
||||
|
@ -28,36 +28,39 @@ func TestSingleNode(t *testing.T) {
|
||||
c.SyncCluster()
|
||||
// Test Set
|
||||
result, err := c.Set("foo", "bar", 100)
|
||||
node := result.Node
|
||||
|
||||
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 {
|
||||
if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
|
||||
if err != nil {
|
||||
t.Fatal("Set 1: ", err)
|
||||
}
|
||||
|
||||
t.Fatalf("Set 1 failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
t.Fatalf("Set 1 failed with %s %s %v", node.Key, node.Value, node.TTL)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
result, err = c.Set("foo", "bar", 100)
|
||||
node = result.Node
|
||||
|
||||
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.PrevValue != "bar" || result.TTL != 100 {
|
||||
if err != nil || node.Key != "/foo" || node.Value != "bar" || node.PrevValue != "bar" || node.TTL != 100 {
|
||||
if err != nil {
|
||||
t.Fatal("Set 2: ", err)
|
||||
}
|
||||
t.Fatalf("Set 2 failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
t.Fatalf("Set 2 failed with %s %s %v", node.Key, node.Value, node.TTL)
|
||||
}
|
||||
|
||||
// Add a test-and-set test
|
||||
|
||||
// First, we'll test we can change the value if we get it write
|
||||
result, err = c.CompareAndSwap("foo", "foobar", 100, "bar", 0)
|
||||
node = result.Node
|
||||
|
||||
if err != nil || result.Key != "/foo" || result.Value != "foobar" || result.PrevValue != "bar" || result.TTL != 100 {
|
||||
if err != nil || node.Key != "/foo" || node.Value != "foobar" || node.PrevValue != "bar" || node.TTL != 100 {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Fatalf("Set 3 failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
t.Fatalf("Set 3 failed with %s %s %v", node.Key, node.Value, node.TTL)
|
||||
}
|
||||
|
||||
// Next, we'll make sure we can't set it without the correct prior value
|
||||
|
@ -44,7 +44,7 @@ func Set(stop chan bool) {
|
||||
|
||||
result, err := c.Set(key, "bar", 0)
|
||||
|
||||
if err != nil || result.Key != "/"+key || result.Value != "bar" {
|
||||
if err != nil || result.Node.Key != "/"+key || result.Node.Value != "bar" {
|
||||
select {
|
||||
case <-stop:
|
||||
stopSet = true
|
||||
|
@ -99,5 +99,5 @@ func TestV1ClusterMigration(t *testing.T) {
|
||||
body = tests.ReadBody(resp)
|
||||
assert.Nil(t, err, "")
|
||||
assert.Equal(t, resp.StatusCode, 200, "")
|
||||
assert.Equal(t, string(body), `{"action":"get","key":"/foo","value":"one","modifiedIndex":9}`)
|
||||
assert.Equal(t, string(body), `{"action":"get","node":{"key":"/foo","value":"one","modifiedIndex":9,"createdIndex":9}}`)
|
||||
}
|
||||
|
@ -2,10 +2,22 @@ package etcd
|
||||
|
||||
// Add a new directory with a random etcd-generated key under the given path.
|
||||
func (c *Client) AddChildDir(key string, ttl uint64) (*Response, error) {
|
||||
return c.post(key, "", ttl)
|
||||
raw, err := c.post(key, "", ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
||||
// Add a new file with a random etcd-generated key under the given path.
|
||||
func (c *Client) AddChild(key string, value string, ttl uint64) (*Response, error) {
|
||||
return c.post(key, value, ttl)
|
||||
raw, err := c.post(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ import "testing"
|
||||
func TestAddChild(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("fooDir")
|
||||
c.DeleteAll("nonexistentDir")
|
||||
c.Delete("fooDir", true)
|
||||
c.Delete("nonexistentDir", true)
|
||||
}()
|
||||
|
||||
c.SetDir("fooDir", 5)
|
||||
@ -21,10 +21,10 @@ func TestAddChild(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := c.Get("fooDir", true)
|
||||
resp, err := c.Get("fooDir", true, false)
|
||||
// The child with v0 should proceed the child with v1 because it's added
|
||||
// earlier, so it should have a lower key.
|
||||
if !(len(resp.Kvs) == 2 && (resp.Kvs[0].Value == "v0" && resp.Kvs[1].Value == "v1")) {
|
||||
if !(len(resp.Node.Nodes) == 2 && (resp.Node.Nodes[0].Value == "v0" && resp.Node.Nodes[1].Value == "v1")) {
|
||||
t.Fatalf("AddChild 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+
|
||||
" The response was: %#v", resp)
|
||||
}
|
||||
@ -40,8 +40,8 @@ func TestAddChild(t *testing.T) {
|
||||
func TestAddChildDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("fooDir")
|
||||
c.DeleteAll("nonexistentDir")
|
||||
c.Delete("fooDir", true)
|
||||
c.Delete("nonexistentDir", true)
|
||||
}()
|
||||
|
||||
c.SetDir("fooDir", 5)
|
||||
@ -56,10 +56,10 @@ func TestAddChildDir(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := c.Get("fooDir", true)
|
||||
resp, err := c.Get("fooDir", true, false)
|
||||
// The child with v0 should proceed the child with v1 because it's added
|
||||
// earlier, so it should have a lower key.
|
||||
if !(len(resp.Kvs) == 2 && (len(resp.Kvs[0].KVPairs) == 0 && len(resp.Kvs[1].KVPairs) == 0)) {
|
||||
if !(len(resp.Node.Nodes) == 2 && (len(resp.Node.Nodes[0].Nodes) == 0 && len(resp.Node.Nodes[1].Nodes) == 0)) {
|
||||
t.Fatalf("AddChildDir 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+
|
||||
" The response was: %#v", resp)
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@ -30,6 +29,10 @@ const (
|
||||
WEAK_CONSISTENCY = "WEAK"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBufferSize = 10
|
||||
)
|
||||
|
||||
type Cluster struct {
|
||||
Leader string `json:"leader"`
|
||||
Machines []string `json:"machines"`
|
||||
@ -48,14 +51,9 @@ type Client struct {
|
||||
config Config `json:"config"`
|
||||
httpClient *http.Client
|
||||
persistence io.Writer
|
||||
cURLch chan string
|
||||
}
|
||||
|
||||
type options map[string]interface{}
|
||||
|
||||
// An internally-used data structure that represents a mapping
|
||||
// between valid options and their kinds
|
||||
type validOptions map[string]reflect.Kind
|
||||
|
||||
// NewClient create a basic client that is configured to be used
|
||||
// with the given machine list.
|
||||
func NewClient(machines []string) *Client {
|
||||
@ -333,9 +331,7 @@ func dialTimeout(network, addr string) (net.Conn, error) {
|
||||
return net.DialTimeout(network, addr, time.Second)
|
||||
}
|
||||
|
||||
func (c *Client) updateLeader(httpPath string) {
|
||||
u, _ := url.Parse(httpPath)
|
||||
|
||||
func (c *Client) updateLeader(u *url.URL) {
|
||||
var leader string
|
||||
if u.Scheme == "" {
|
||||
leader = "http://" + u.Host
|
||||
@ -347,3 +343,32 @@ func (c *Client) updateLeader(httpPath string) {
|
||||
c.cluster.Leader = leader
|
||||
c.saveConfig()
|
||||
}
|
||||
|
||||
// switchLeader switch the current leader to machines[num]
|
||||
func (c *Client) switchLeader(num int) {
|
||||
logger.Debugf("switch.leader[from %v to %v]",
|
||||
c.cluster.Leader, c.cluster.Machines[num])
|
||||
|
||||
c.cluster.Leader = c.cluster.Machines[num]
|
||||
}
|
||||
|
||||
func (c *Client) OpenCURL() {
|
||||
c.cURLch = make(chan string, defaultBufferSize)
|
||||
}
|
||||
|
||||
func (c *Client) CloseCURL() {
|
||||
c.cURLch = nil
|
||||
}
|
||||
|
||||
func (c *Client) sendCURL(command string) {
|
||||
go func() {
|
||||
select {
|
||||
case c.cURLch <- command:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Client) RecvCURL() string {
|
||||
return <-c.cURLch
|
||||
}
|
||||
|
@ -14,5 +14,12 @@ func (c *Client) CompareAndSwap(key string, value string, ttl uint64, prevValue
|
||||
if prevIndex != 0 {
|
||||
options["prevIndex"] = prevIndex
|
||||
}
|
||||
return c.put(key, value, ttl, options)
|
||||
|
||||
raw, err := c.put(key, value, ttl, options)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
func TestCompareAndSwap(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("foo")
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
@ -17,8 +17,8 @@ func TestCompareAndSwap(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Value == "bar2" && resp.PrevValue == "bar" &&
|
||||
resp.Key == "/foo" && resp.TTL == 5) {
|
||||
if !(resp.Node.Value == "bar2" && resp.Node.PrevValue == "bar" &&
|
||||
resp.Node.Key == "/foo" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
@ -34,12 +34,12 @@ func TestCompareAndSwap(t *testing.T) {
|
||||
}
|
||||
|
||||
// This should succeed
|
||||
resp, err = c.CompareAndSwap("foo", "bar2", 5, "", resp.ModifiedIndex)
|
||||
resp, err = c.CompareAndSwap("foo", "bar2", 5, "", resp.Node.ModifiedIndex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Value == "bar2" && resp.PrevValue == "bar" &&
|
||||
resp.Key == "/foo" && resp.TTL == 5) {
|
||||
if !(resp.Node.Value == "bar2" && resp.Node.PrevValue == "bar" &&
|
||||
resp.Node.Key == "/foo" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,15 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"github.com/coreos/go-log/log"
|
||||
"os"
|
||||
|
||||
"github.com/coreos/go-log/log"
|
||||
)
|
||||
|
||||
var logger *log.Logger
|
||||
|
||||
func init() {
|
||||
setLogger(log.PriErr)
|
||||
// Uncomment the following line if you want to see lots of logs
|
||||
// OpenDebug()
|
||||
}
|
||||
|
||||
func OpenDebug() {
|
||||
|
@ -1,17 +1,26 @@
|
||||
package etcd
|
||||
|
||||
// DeleteAll deletes everything under the given key. If the key
|
||||
// points to a file, the file will be deleted. If the key points
|
||||
// to a directory, then everything under the directory, include
|
||||
// Delete deletes the given key.
|
||||
// When recursive set to false If the key points to a
|
||||
// directory, the method will fail.
|
||||
// When recursive set to true, if the key points to a file,
|
||||
// the file will be deleted. If the key points
|
||||
// to a directory, then everything under the directory, including
|
||||
// all child directories, will be deleted.
|
||||
func (c *Client) DeleteAll(key string) (*Response, error) {
|
||||
return c.delete(key, options{
|
||||
"recursive": true,
|
||||
})
|
||||
func (c *Client) Delete(key string, recursive bool) (*Response, error) {
|
||||
raw, err := c.DeleteRaw(key, recursive)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
||||
// Delete deletes the given key. If the key points to a
|
||||
// directory, the method will fail.
|
||||
func (c *Client) Delete(key string) (*Response, error) {
|
||||
return c.delete(key, nil)
|
||||
func (c *Client) DeleteRaw(key string, recursive bool) (*RawResponse, error) {
|
||||
ops := options{
|
||||
"recursive": recursive,
|
||||
}
|
||||
|
||||
return c.delete(key, ops)
|
||||
}
|
||||
|
@ -7,21 +7,21 @@ import (
|
||||
func TestDelete(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("foo")
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
resp, err := c.Delete("foo")
|
||||
resp, err := c.Delete("foo", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.PrevValue == "bar" && resp.Value == "") {
|
||||
t.Fatalf("Delete failed with %s %s", resp.PrevValue,
|
||||
resp.Value)
|
||||
if !(resp.Node.PrevValue == "bar" && resp.Node.Value == "") {
|
||||
t.Fatalf("Delete failed with %s %s", resp.Node.PrevValue,
|
||||
resp.Node.Value)
|
||||
}
|
||||
|
||||
resp, err = c.Delete("foo")
|
||||
resp, err = c.Delete("foo", false)
|
||||
if err == nil {
|
||||
t.Fatalf("Delete should have failed because the key foo did not exist. "+
|
||||
"The response was: %v", resp)
|
||||
@ -31,32 +31,32 @@ func TestDelete(t *testing.T) {
|
||||
func TestDeleteAll(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("foo")
|
||||
c.DeleteAll("fooDir")
|
||||
c.Delete("foo", true)
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
resp, err := c.DeleteAll("foo")
|
||||
resp, err := c.Delete("foo", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.PrevValue == "bar" && resp.Value == "") {
|
||||
if !(resp.Node.PrevValue == "bar" && resp.Node.Value == "") {
|
||||
t.Fatalf("DeleteAll 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
c.SetDir("fooDir", 5)
|
||||
c.Set("fooDir/foo", "bar", 5)
|
||||
resp, err = c.DeleteAll("fooDir")
|
||||
resp, err = c.Delete("fooDir", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.PrevValue == "" && resp.Value == "") {
|
||||
if !(resp.Node.PrevValue == "" && resp.Node.Value == "") {
|
||||
t.Fatalf("DeleteAll 2 failed: %#v", resp)
|
||||
}
|
||||
|
||||
resp, err = c.DeleteAll("foo")
|
||||
resp, err = c.Delete("foo", true)
|
||||
if err == nil {
|
||||
t.Fatalf("DeleteAll should have failed because the key foo did not exist. "+
|
||||
"The response was: %v", resp)
|
||||
|
@ -1,23 +1,27 @@
|
||||
package etcd
|
||||
|
||||
// GetDir gets the all contents under the given key.
|
||||
// If the key points to a file, the file is returned.
|
||||
// If the key points to a directory, everything under it is returnd,
|
||||
// including all contents under all child directories.
|
||||
func (c *Client) GetAll(key string, sort bool) (*Response, error) {
|
||||
return c.get(key, options{
|
||||
"recursive": true,
|
||||
"sorted": sort,
|
||||
})
|
||||
}
|
||||
|
||||
// Get gets the file or directory associated with the given key.
|
||||
// If the key points to a directory, files and directories under
|
||||
// it will be returned in sorted or unsorted order, depending on
|
||||
// the sort flag. Note that contents under child directories
|
||||
// will not be returned. To get those contents, use GetAll.
|
||||
func (c *Client) Get(key string, sort bool) (*Response, error) {
|
||||
return c.get(key, options{
|
||||
"sorted": sort,
|
||||
})
|
||||
// the sort flag.
|
||||
// If recursive is set to false, contents under child directories
|
||||
// will not be returned.
|
||||
// If recursive is set to true, all the contents will be returned.
|
||||
func (c *Client) Get(key string, sort, recursive bool) (*Response, error) {
|
||||
raw, err := c.RawGet(key, sort, recursive)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
||||
func (c *Client) RawGet(key string, sort, recursive bool) (*RawResponse, error) {
|
||||
ops := options{
|
||||
"recursive": recursive,
|
||||
"sorted": sort,
|
||||
}
|
||||
|
||||
return c.get(key, ops)
|
||||
}
|
||||
|
@ -8,22 +8,22 @@ import (
|
||||
func TestGet(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("foo")
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
|
||||
result, err := c.Get("foo", false)
|
||||
result, err := c.Get("foo", false, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result.Key != "/foo" || result.Value != "bar" {
|
||||
t.Fatalf("Get failed with %s %s %v", result.Key, result.Value, result.TTL)
|
||||
if result.Node.Key != "/foo" || result.Node.Value != "bar" {
|
||||
t.Fatalf("Get failed with %s %s %v", result.Node.Key, result.Node.Value, result.Node.TTL)
|
||||
}
|
||||
|
||||
result, err = c.Get("goo", false)
|
||||
result, err = c.Get("goo", false, false)
|
||||
if err == nil {
|
||||
t.Fatalf("should not be able to get non-exist key")
|
||||
}
|
||||
@ -32,7 +32,7 @@ func TestGet(t *testing.T) {
|
||||
func TestGetAll(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("fooDir")
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
c.SetDir("fooDir", 5)
|
||||
@ -40,25 +40,36 @@ func TestGetAll(t *testing.T) {
|
||||
c.Set("fooDir/k1", "v1", 5)
|
||||
|
||||
// Return kv-pairs in sorted order
|
||||
result, err := c.Get("fooDir", true)
|
||||
result, err := c.Get("fooDir", true, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := kvPairs{
|
||||
KeyValuePair{
|
||||
Key: "/fooDir/k0",
|
||||
Value: "v0",
|
||||
expected := Nodes{
|
||||
Node{
|
||||
Key: "/fooDir/k0",
|
||||
Value: "v0",
|
||||
TTL: 5,
|
||||
ModifiedIndex: 31,
|
||||
CreatedIndex: 31,
|
||||
},
|
||||
KeyValuePair{
|
||||
Key: "/fooDir/k1",
|
||||
Value: "v1",
|
||||
Node{
|
||||
Key: "/fooDir/k1",
|
||||
Value: "v1",
|
||||
TTL: 5,
|
||||
ModifiedIndex: 32,
|
||||
CreatedIndex: 32,
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result.Kvs, expected) {
|
||||
t.Fatalf("(actual) %v != (expected) %v", result.Kvs, expected)
|
||||
// do not check expiration time, too hard to fake
|
||||
for i, _ := range result.Node.Nodes {
|
||||
result.Node.Nodes[i].Expiration = nil
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result.Node.Nodes, expected) {
|
||||
t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected)
|
||||
}
|
||||
|
||||
// Test the `recursive` option
|
||||
@ -66,34 +77,44 @@ func TestGetAll(t *testing.T) {
|
||||
c.Set("fooDir/childDir/k2", "v2", 5)
|
||||
|
||||
// Return kv-pairs in sorted order
|
||||
result, err = c.GetAll("fooDir", true)
|
||||
result, err = c.Get("fooDir", true, true)
|
||||
|
||||
// do not check expiration time, too hard to fake
|
||||
result.Node.Expiration = nil
|
||||
for i, _ := range result.Node.Nodes {
|
||||
result.Node.Nodes[i].Expiration = nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected = kvPairs{
|
||||
KeyValuePair{
|
||||
expected = Nodes{
|
||||
Node{
|
||||
Key: "/fooDir/childDir",
|
||||
Dir: true,
|
||||
KVPairs: kvPairs{
|
||||
KeyValuePair{
|
||||
Nodes: Nodes{
|
||||
Node{
|
||||
Key: "/fooDir/childDir/k2",
|
||||
Value: "v2",
|
||||
TTL: 5,
|
||||
},
|
||||
},
|
||||
TTL: 5,
|
||||
},
|
||||
KeyValuePair{
|
||||
Node{
|
||||
Key: "/fooDir/k0",
|
||||
Value: "v0",
|
||||
TTL: 5,
|
||||
},
|
||||
KeyValuePair{
|
||||
Node{
|
||||
Key: "/fooDir/k1",
|
||||
Value: "v1",
|
||||
TTL: 5,
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result.Kvs, expected) {
|
||||
t.Fatalf("(actual) %v != (expected) %v", result.Kvs)
|
||||
if !reflect.DeepEqual(result.Node.Nodes, expected) {
|
||||
t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected)
|
||||
}
|
||||
}
|
||||
|
68
third_party/github.com/coreos/go-etcd/etcd/options.go
vendored
Normal file
68
third_party/github.com/coreos/go-etcd/etcd/options.go
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type options map[string]interface{}
|
||||
|
||||
// An internally-used data structure that represents a mapping
|
||||
// between valid options and their kinds
|
||||
type validOptions map[string]reflect.Kind
|
||||
|
||||
// Valid options for GET, PUT, POST, DELETE
|
||||
// Using CAPITALIZED_UNDERSCORE to emphasize that these
|
||||
// values are meant to be used as constants.
|
||||
var (
|
||||
VALID_GET_OPTIONS = validOptions{
|
||||
"recursive": reflect.Bool,
|
||||
"consistent": reflect.Bool,
|
||||
"sorted": reflect.Bool,
|
||||
"wait": reflect.Bool,
|
||||
"waitIndex": reflect.Uint64,
|
||||
}
|
||||
|
||||
VALID_PUT_OPTIONS = validOptions{
|
||||
"prevValue": reflect.String,
|
||||
"prevIndex": reflect.Uint64,
|
||||
"prevExist": reflect.Bool,
|
||||
}
|
||||
|
||||
VALID_POST_OPTIONS = validOptions{}
|
||||
|
||||
VALID_DELETE_OPTIONS = validOptions{
|
||||
"recursive": reflect.Bool,
|
||||
}
|
||||
)
|
||||
|
||||
// Convert options to a string of HTML parameters
|
||||
func (ops options) toParameters(validOps validOptions) (string, error) {
|
||||
p := "?"
|
||||
values := url.Values{}
|
||||
|
||||
if ops == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
for k, v := range ops {
|
||||
// Check if the given option is valid (that it exists)
|
||||
kind := validOps[k]
|
||||
if kind == reflect.Invalid {
|
||||
return "", fmt.Errorf("Invalid option: %v", k)
|
||||
}
|
||||
|
||||
// Check if the given option is of the valid type
|
||||
t := reflect.TypeOf(v)
|
||||
if kind != t.Kind() {
|
||||
return "", fmt.Errorf("Option %s should be of %v kind, not of %v kind.",
|
||||
k, kind, t.Kind())
|
||||
}
|
||||
|
||||
values.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
p += values.Encode()
|
||||
return p, nil
|
||||
}
|
@ -1,54 +1,18 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Valid options for GET, PUT, POST, DELETE
|
||||
// Using CAPITALIZED_UNDERSCORE to emphasize that these
|
||||
// values are meant to be used as constants.
|
||||
var (
|
||||
VALID_GET_OPTIONS = validOptions{
|
||||
"recursive": reflect.Bool,
|
||||
"consistent": reflect.Bool,
|
||||
"sorted": reflect.Bool,
|
||||
"wait": reflect.Bool,
|
||||
"waitIndex": reflect.Uint64,
|
||||
}
|
||||
|
||||
VALID_PUT_OPTIONS = validOptions{
|
||||
"prevValue": reflect.String,
|
||||
"prevIndex": reflect.Uint64,
|
||||
"prevExist": reflect.Bool,
|
||||
}
|
||||
|
||||
VALID_POST_OPTIONS = validOptions{}
|
||||
|
||||
VALID_DELETE_OPTIONS = validOptions{
|
||||
"recursive": reflect.Bool,
|
||||
}
|
||||
|
||||
curlChan chan string
|
||||
)
|
||||
|
||||
// SetCurlChan sets a channel to which cURL commands which can be used to
|
||||
// re-produce requests are sent. This is useful for debugging.
|
||||
func SetCurlChan(c chan string) {
|
||||
curlChan = c
|
||||
}
|
||||
|
||||
// get issues a GET request
|
||||
func (c *Client) get(key string, options options) (*Response, error) {
|
||||
func (c *Client) get(key string, options options) (*RawResponse, error) {
|
||||
logger.Debugf("get %s [%s]", key, c.cluster.Leader)
|
||||
|
||||
p := path.Join("keys", key)
|
||||
@ -57,15 +21,14 @@ func (c *Client) get(key string, options options) (*Response, error) {
|
||||
if c.config.Consistency == STRONG_CONSISTENCY {
|
||||
options["consistent"] = true
|
||||
}
|
||||
if options != nil {
|
||||
str, err := optionsToString(options, VALID_GET_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
}
|
||||
|
||||
resp, err := c.sendRequest("GET", p, url.Values{})
|
||||
str, err := options.toParameters(VALID_GET_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
|
||||
resp, err := c.sendRequest("GET", p, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -75,28 +38,19 @@ func (c *Client) get(key string, options options) (*Response, error) {
|
||||
}
|
||||
|
||||
// put issues a PUT request
|
||||
func (c *Client) put(key string, value string, ttl uint64, options options) (*Response, error) {
|
||||
func (c *Client) put(key string, value string, ttl uint64,
|
||||
options options) (*RawResponse, error) {
|
||||
|
||||
logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader)
|
||||
v := url.Values{}
|
||||
|
||||
if value != "" {
|
||||
v.Set("value", value)
|
||||
}
|
||||
|
||||
if ttl > 0 {
|
||||
v.Set("ttl", fmt.Sprintf("%v", ttl))
|
||||
}
|
||||
|
||||
p := path.Join("keys", key)
|
||||
if options != nil {
|
||||
str, err := optionsToString(options, VALID_PUT_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
}
|
||||
|
||||
resp, err := c.sendRequest("PUT", p, v)
|
||||
str, err := options.toParameters(VALID_PUT_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
|
||||
resp, err := c.sendRequest("PUT", p, buildValues(value, ttl))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -106,19 +60,11 @@ func (c *Client) put(key string, value string, ttl uint64, options options) (*Re
|
||||
}
|
||||
|
||||
// post issues a POST request
|
||||
func (c *Client) post(key string, value string, ttl uint64) (*Response, error) {
|
||||
func (c *Client) post(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader)
|
||||
v := url.Values{}
|
||||
p := path.Join("keys", key)
|
||||
|
||||
if value != "" {
|
||||
v.Set("value", value)
|
||||
}
|
||||
|
||||
if ttl > 0 {
|
||||
v.Set("ttl", fmt.Sprintf("%v", ttl))
|
||||
}
|
||||
|
||||
resp, err := c.sendRequest("POST", path.Join("keys", key), v)
|
||||
resp, err := c.sendRequest("POST", p, buildValues(value, ttl))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -128,20 +74,18 @@ func (c *Client) post(key string, value string, ttl uint64) (*Response, error) {
|
||||
}
|
||||
|
||||
// delete issues a DELETE request
|
||||
func (c *Client) delete(key string, options options) (*Response, error) {
|
||||
func (c *Client) delete(key string, options options) (*RawResponse, error) {
|
||||
logger.Debugf("delete %s [%s]", key, c.cluster.Leader)
|
||||
v := url.Values{}
|
||||
|
||||
p := path.Join("keys", key)
|
||||
if options != nil {
|
||||
str, err := optionsToString(options, VALID_DELETE_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
}
|
||||
|
||||
resp, err := c.sendRequest("DELETE", p, v)
|
||||
str, err := options.toParameters(VALID_DELETE_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
|
||||
resp, err := c.sendRequest("DELETE", p, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -151,126 +95,128 @@ func (c *Client) delete(key string, options options) (*Response, error) {
|
||||
}
|
||||
|
||||
// sendRequest sends a HTTP request and returns a Response as defined by etcd
|
||||
func (c *Client) sendRequest(method string, _path string, values url.Values) (*Response, error) {
|
||||
var body string = values.Encode()
|
||||
var resp *http.Response
|
||||
var req *http.Request
|
||||
func (c *Client) sendRequest(method string, relativePath string,
|
||||
values url.Values) (*RawResponse, error) {
|
||||
|
||||
var req *http.Request
|
||||
var resp *http.Response
|
||||
var httpPath string
|
||||
var err error
|
||||
var b []byte
|
||||
|
||||
trial := 0
|
||||
|
||||
retry := 0
|
||||
// if we connect to a follower, we will retry until we found a leader
|
||||
for {
|
||||
var httpPath string
|
||||
|
||||
// If _path has schema already, then it's assumed to be
|
||||
// a complete URL and therefore needs no further processing.
|
||||
u, err := url.Parse(_path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
trial++
|
||||
logger.Debug("begin trail ", trial)
|
||||
if trial > 2*len(c.cluster.Machines) {
|
||||
return nil, fmt.Errorf("Cannot reach servers after %v time", trial)
|
||||
}
|
||||
|
||||
if u.Scheme != "" {
|
||||
httpPath = _path
|
||||
if method == "GET" && c.config.Consistency == WEAK_CONSISTENCY {
|
||||
// If it's a GET and consistency level is set to WEAK,
|
||||
// then use a random machine.
|
||||
httpPath = c.getHttpPath(true, relativePath)
|
||||
} else {
|
||||
if method == "GET" && c.config.Consistency == WEAK_CONSISTENCY {
|
||||
// If it's a GET and consistency level is set to WEAK,
|
||||
// then use a random machine.
|
||||
httpPath = c.getHttpPath(true, _path)
|
||||
} else {
|
||||
// Else use the leader.
|
||||
httpPath = c.getHttpPath(false, _path)
|
||||
}
|
||||
// Else use the leader.
|
||||
httpPath = c.getHttpPath(false, relativePath)
|
||||
}
|
||||
|
||||
// Return a cURL command if curlChan is set
|
||||
if curlChan != nil {
|
||||
if c.cURLch != nil {
|
||||
command := fmt.Sprintf("curl -X %s %s", method, httpPath)
|
||||
for key, value := range values {
|
||||
command += fmt.Sprintf(" -d %s=%s", key, value[0])
|
||||
}
|
||||
curlChan <- command
|
||||
c.sendCURL(command)
|
||||
}
|
||||
|
||||
logger.Debug("send.request.to ", httpPath, " | method ", method)
|
||||
if body == "" {
|
||||
|
||||
if values == nil {
|
||||
req, _ = http.NewRequest(method, httpPath, nil)
|
||||
|
||||
} else {
|
||||
req, _ = http.NewRequest(method, httpPath, strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
|
||||
req, _ = http.NewRequest(method, httpPath,
|
||||
strings.NewReader(values.Encode()))
|
||||
|
||||
req.Header.Set("Content-Type",
|
||||
"application/x-www-form-urlencoded; param=value")
|
||||
}
|
||||
|
||||
resp, err = c.httpClient.Do(req)
|
||||
|
||||
logger.Debug("recv.response.from ", httpPath)
|
||||
// network error, change a machine!
|
||||
if err != nil {
|
||||
retry++
|
||||
if retry > 2*len(c.cluster.Machines) {
|
||||
return nil, errors.New("Cannot reach servers")
|
||||
}
|
||||
num := retry % len(c.cluster.Machines)
|
||||
logger.Debug("update.leader[", c.cluster.Leader, ",", c.cluster.Machines[num], "]")
|
||||
c.cluster.Leader = c.cluster.Machines[num]
|
||||
if resp, err = c.httpClient.Do(req); err != nil {
|
||||
c.switchLeader(trial % len(c.cluster.Machines))
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
continue
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
if resp.StatusCode == http.StatusTemporaryRedirect {
|
||||
httpPath := resp.Header.Get("Location")
|
||||
logger.Debug("recv.response.from ", httpPath)
|
||||
|
||||
resp.Body.Close()
|
||||
var ok bool
|
||||
ok, b = c.handleResp(resp)
|
||||
|
||||
if httpPath == "" {
|
||||
return nil, errors.New("Cannot get redirection location")
|
||||
}
|
||||
|
||||
c.updateLeader(httpPath)
|
||||
logger.Debug("send.redirect")
|
||||
// try to connect the leader
|
||||
if !ok {
|
||||
continue
|
||||
} else if resp.StatusCode == http.StatusInternalServerError {
|
||||
resp.Body.Close()
|
||||
|
||||
retry++
|
||||
if retry > 2*len(c.cluster.Machines) {
|
||||
return nil, errors.New("Cannot reach servers")
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
logger.Debug("send.return.response ", httpPath)
|
||||
break
|
||||
}
|
||||
|
||||
logger.Debug("recv.success.", httpPath)
|
||||
break
|
||||
}
|
||||
logger.Debug("error.from ", httpPath, " ", err.Error())
|
||||
|
||||
// should not reach here
|
||||
// err and resp should not be nil at the same time
|
||||
logger.Debug("error.from ", httpPath)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert HTTP response to etcd response
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
r := &RawResponse{
|
||||
StatusCode: resp.StatusCode,
|
||||
Body: b,
|
||||
Header: resp.Header,
|
||||
}
|
||||
|
||||
if !(resp.StatusCode == http.StatusOK ||
|
||||
resp.StatusCode == http.StatusCreated) {
|
||||
return nil, handleError(b)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// handleResp handles the responses from the etcd server
|
||||
// If status code is OK, read the http body and return it as byte array
|
||||
// If status code is TemporaryRedirect, update leader.
|
||||
// If status code is InternalServerError, sleep for 200ms.
|
||||
func (c *Client) handleResp(resp *http.Response) (bool, []byte) {
|
||||
defer resp.Body.Close()
|
||||
|
||||
code := resp.StatusCode
|
||||
|
||||
if code == http.StatusTemporaryRedirect {
|
||||
u, err := resp.Location()
|
||||
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
} else {
|
||||
c.updateLeader(u)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
||||
} else if code == http.StatusInternalServerError {
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
|
||||
} else if code == http.StatusOK ||
|
||||
code == http.StatusCreated ||
|
||||
code == http.StatusBadRequest {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, b
|
||||
}
|
||||
|
||||
var result Response
|
||||
|
||||
err = json.Unmarshal(b, &result)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
logger.Warning("bad status code ", resp.StatusCode)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *Client) getHttpPath(random bool, s ...string) string {
|
||||
@ -288,3 +234,18 @@ func (c *Client) getHttpPath(random bool, s ...string) string {
|
||||
|
||||
return fullPath
|
||||
}
|
||||
|
||||
// buildValues builds a url.Values map according to the given value and ttl
|
||||
func buildValues(value string, ttl uint64) url.Values {
|
||||
v := url.Values{}
|
||||
|
||||
if value != "" {
|
||||
v.Set("value", value)
|
||||
}
|
||||
|
||||
if ttl > 0 {
|
||||
v.Set("ttl", fmt.Sprintf("%v", ttl))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
@ -1,51 +1,68 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The response object from the server.
|
||||
const (
|
||||
rawResponse = iota
|
||||
normalResponse
|
||||
)
|
||||
|
||||
type responseType int
|
||||
|
||||
type RawResponse struct {
|
||||
StatusCode int
|
||||
Body []byte
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
func (rr *RawResponse) toResponse() (*Response, error) {
|
||||
if rr.StatusCode == http.StatusBadRequest {
|
||||
return nil, handleError(rr.Body)
|
||||
}
|
||||
|
||||
resp := new(Response)
|
||||
|
||||
err := json.Unmarshal(rr.Body, resp)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Action string `json:"action"`
|
||||
Key string `json:"key"`
|
||||
Dir bool `json:"dir,omitempty"`
|
||||
PrevValue string `json:"prevValue,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Kvs kvPairs `json:"kvs,omitempty"`
|
||||
|
||||
// If the key did not exist before the action,
|
||||
// this field should be set to true
|
||||
NewKey bool `json:"newKey,omitempty"`
|
||||
|
||||
Expiration *time.Time `json:"expiration,omitempty"`
|
||||
|
||||
// Time to live in second
|
||||
TTL int64 `json:"ttl,omitempty"`
|
||||
|
||||
// The command index of the raft machine when the command is executed
|
||||
ModifiedIndex uint64 `json:"modifiedIndex"`
|
||||
Action string `json:"action"`
|
||||
Node *Node `json:"node,omitempty"`
|
||||
}
|
||||
|
||||
// When user list a directory, we add all the node into key-value pair slice
|
||||
type KeyValuePair struct {
|
||||
Key string `json:"key, omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Dir bool `json:"dir,omitempty"`
|
||||
KVPairs kvPairs `json:"kvs,omitempty"`
|
||||
TTL int64 `json:"ttl,omitempty"`
|
||||
type Node struct {
|
||||
Key string `json:"key, omitempty"`
|
||||
PrevValue string `json:"prevValue,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Dir bool `json:"dir,omitempty"`
|
||||
Expiration *time.Time `json:"expiration,omitempty"`
|
||||
TTL int64 `json:"ttl,omitempty"`
|
||||
Nodes Nodes `json:"nodes,omitempty"`
|
||||
ModifiedIndex uint64 `json:"modifiedIndex,omitempty"`
|
||||
CreatedIndex uint64 `json:"createdIndex,omitempty"`
|
||||
}
|
||||
|
||||
type kvPairs []KeyValuePair
|
||||
type Nodes []Node
|
||||
|
||||
// interfaces for sorting
|
||||
func (kvs kvPairs) Len() int {
|
||||
return len(kvs)
|
||||
func (ns Nodes) Len() int {
|
||||
return len(ns)
|
||||
}
|
||||
|
||||
func (kvs kvPairs) Less(i, j int) bool {
|
||||
return kvs[i].Key < kvs[j].Key
|
||||
func (ns Nodes) Less(i, j int) bool {
|
||||
return ns[i].Key < ns[j].Key
|
||||
}
|
||||
|
||||
func (kvs kvPairs) Swap(i, j int) {
|
||||
kvs[i], kvs[j] = kvs[j], kvs[i]
|
||||
func (ns Nodes) Swap(i, j int) {
|
||||
ns[i], ns[j] = ns[j], ns[i]
|
||||
}
|
||||
|
@ -7,12 +7,11 @@ import (
|
||||
|
||||
func TestSetCurlChan(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("foo")
|
||||
}()
|
||||
c.OpenCURL()
|
||||
|
||||
curlChan := make(chan string, 1)
|
||||
SetCurlChan(curlChan)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
_, err := c.Set("foo", "bar", 5)
|
||||
if err != nil {
|
||||
@ -21,21 +20,21 @@ func TestSetCurlChan(t *testing.T) {
|
||||
|
||||
expected := fmt.Sprintf("curl -X PUT %s/v2/keys/foo -d value=bar -d ttl=5",
|
||||
c.cluster.Leader)
|
||||
actual := <-curlChan
|
||||
actual := c.RecvCURL()
|
||||
if expected != actual {
|
||||
t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
|
||||
actual, expected)
|
||||
}
|
||||
|
||||
c.SetConsistency(STRONG_CONSISTENCY)
|
||||
_, err = c.Get("foo", false)
|
||||
_, err = c.Get("foo", false, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected = fmt.Sprintf("curl -X GET %s/v2/keys/foo?consistent=true&sorted=false",
|
||||
expected = fmt.Sprintf("curl -X GET %s/v2/keys/foo?consistent=true&recursive=false&sorted=false",
|
||||
c.cluster.Leader)
|
||||
actual = <-curlChan
|
||||
actual = c.RecvCURL()
|
||||
if expected != actual {
|
||||
t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
|
||||
actual, expected)
|
||||
|
@ -2,42 +2,110 @@ package etcd
|
||||
|
||||
// SetDir sets the given key to a directory.
|
||||
func (c *Client) SetDir(key string, ttl uint64) (*Response, error) {
|
||||
return c.put(key, "", ttl, nil)
|
||||
raw, err := c.RawSetDir(key, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
||||
// UpdateDir updates the given key to a directory. It succeeds only if the
|
||||
// given key already exists.
|
||||
func (c *Client) UpdateDir(key string, ttl uint64) (*Response, error) {
|
||||
return c.put(key, "", ttl, options{
|
||||
"prevExist": true,
|
||||
})
|
||||
raw, err := c.RawUpdateDir(key, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
||||
// UpdateDir creates a directory under the given key. It succeeds only if
|
||||
// the given key does not yet exist.
|
||||
func (c *Client) CreateDir(key string, ttl uint64) (*Response, error) {
|
||||
return c.put(key, "", ttl, options{
|
||||
"prevExist": false,
|
||||
})
|
||||
raw, err := c.RawCreateDir(key, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
||||
// Set sets the given key to the given value.
|
||||
func (c *Client) Set(key string, value string, ttl uint64) (*Response, error) {
|
||||
return c.put(key, value, ttl, nil)
|
||||
raw, err := c.RawSet(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
||||
// Update updates the given key to the given value. It succeeds only if the
|
||||
// given key already exists.
|
||||
func (c *Client) Update(key string, value string, ttl uint64) (*Response, error) {
|
||||
return c.put(key, value, ttl, options{
|
||||
"prevExist": true,
|
||||
})
|
||||
raw, err := c.RawUpdate(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
||||
// Create creates a file with the given value under the given key. It succeeds
|
||||
// only if the given key does not yet exist.
|
||||
func (c *Client) Create(key string, value string, ttl uint64) (*Response, error) {
|
||||
return c.put(key, value, ttl, options{
|
||||
"prevExist": false,
|
||||
})
|
||||
raw, err := c.RawCreate(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
||||
func (c *Client) RawSetDir(key string, ttl uint64) (*RawResponse, error) {
|
||||
return c.put(key, "", ttl, nil)
|
||||
}
|
||||
|
||||
func (c *Client) RawUpdateDir(key string, ttl uint64) (*RawResponse, error) {
|
||||
ops := options{
|
||||
"prevExist": true,
|
||||
}
|
||||
|
||||
return c.put(key, "", ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawCreateDir(key string, ttl uint64) (*RawResponse, error) {
|
||||
ops := options{
|
||||
"prevExist": false,
|
||||
}
|
||||
|
||||
return c.put(key, "", ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawSet(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
return c.put(key, value, ttl, nil)
|
||||
}
|
||||
|
||||
func (c *Client) RawUpdate(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
ops := options{
|
||||
"prevExist": true,
|
||||
}
|
||||
|
||||
return c.put(key, value, ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawCreate(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
ops := options{
|
||||
"prevExist": false,
|
||||
}
|
||||
|
||||
return c.put(key, value, ttl, ops)
|
||||
}
|
||||
|
@ -7,14 +7,14 @@ import (
|
||||
func TestSet(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("foo")
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
resp, err := c.Set("foo", "bar", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Key != "/foo" || resp.Value != "bar" || resp.TTL != 5 {
|
||||
if resp.Node.Key != "/foo" || resp.Node.Value != "bar" || resp.Node.TTL != 5 {
|
||||
t.Fatalf("Set 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
@ -22,8 +22,8 @@ func TestSet(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Key == "/foo" && resp.Value == "bar2" &&
|
||||
resp.PrevValue == "bar" && resp.TTL == 5) {
|
||||
if !(resp.Node.Key == "/foo" && resp.Node.Value == "bar2" &&
|
||||
resp.Node.PrevValue == "bar" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Set 2 failed: %#v", resp)
|
||||
}
|
||||
}
|
||||
@ -31,12 +31,12 @@ func TestSet(t *testing.T) {
|
||||
func TestUpdate(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("foo")
|
||||
c.DeleteAll("nonexistent")
|
||||
c.Delete("foo", true)
|
||||
c.Delete("nonexistent", true)
|
||||
}()
|
||||
|
||||
resp, err := c.Set("foo", "bar", 5)
|
||||
t.Logf("%#v", resp)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -47,8 +47,8 @@ func TestUpdate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "update" && resp.Key == "/foo" &&
|
||||
resp.PrevValue == "bar" && resp.TTL == 5) {
|
||||
if !(resp.Action == "update" && resp.Node.Key == "/foo" &&
|
||||
resp.Node.PrevValue == "bar" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Update 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
@ -56,14 +56,14 @@ func TestUpdate(t *testing.T) {
|
||||
resp, err = c.Update("nonexistent", "whatever", 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did not exist, so the update should have failed."+
|
||||
"The response was: %#v", resp.Key, resp)
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("newKey")
|
||||
c.Delete("newKey", true)
|
||||
}()
|
||||
|
||||
newKey := "/newKey"
|
||||
@ -75,8 +75,8 @@ func TestCreate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "create" && resp.Key == newKey &&
|
||||
resp.Value == newValue && resp.PrevValue == "" && resp.TTL == 5) {
|
||||
if !(resp.Action == "create" && resp.Node.Key == newKey &&
|
||||
resp.Node.Value == newValue && resp.Node.PrevValue == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Create 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
@ -84,22 +84,22 @@ func TestCreate(t *testing.T) {
|
||||
resp, err = c.Create(newKey, newValue, 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did exist, so the creation should have failed."+
|
||||
"The response was: %#v", resp.Key, resp)
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("foo")
|
||||
c.DeleteAll("fooDir")
|
||||
c.Delete("foo", true)
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
resp, err := c.SetDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Key == "/fooDir" && resp.Value == "" && resp.TTL == 5) {
|
||||
if !(resp.Node.Key == "/fooDir" && resp.Node.Value == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("SetDir 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
@ -120,8 +120,8 @@ func TestSetDir(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Key == "/foo" && resp.Value == "" &&
|
||||
resp.PrevValue == "bar" && resp.TTL == 5) {
|
||||
if !(resp.Node.Key == "/foo" && resp.Node.Value == "" &&
|
||||
resp.Node.PrevValue == "bar" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("SetDir 2 failed: %#v", resp)
|
||||
}
|
||||
}
|
||||
@ -129,11 +129,10 @@ func TestSetDir(t *testing.T) {
|
||||
func TestUpdateDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("fooDir")
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
resp, err := c.SetDir("fooDir", 5)
|
||||
t.Logf("%#v", resp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -144,8 +143,8 @@ func TestUpdateDir(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "update" && resp.Key == "/fooDir" &&
|
||||
resp.Value == "" && resp.PrevValue == "" && resp.TTL == 5) {
|
||||
if !(resp.Action == "update" && resp.Node.Key == "/fooDir" &&
|
||||
resp.Node.Value == "" && resp.Node.PrevValue == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("UpdateDir 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
@ -153,14 +152,14 @@ func TestUpdateDir(t *testing.T) {
|
||||
resp, err = c.UpdateDir("nonexistentDir", 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did not exist, so the update should have failed."+
|
||||
"The response was: %#v", resp.Key, resp)
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("fooDir")
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
// This should succeed
|
||||
@ -169,8 +168,8 @@ func TestCreateDir(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "create" && resp.Key == "/fooDir" &&
|
||||
resp.Value == "" && resp.PrevValue == "" && resp.TTL == 5) {
|
||||
if !(resp.Action == "create" && resp.Node.Key == "/fooDir" &&
|
||||
resp.Node.Value == "" && resp.Node.PrevValue == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("CreateDir 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
@ -178,6 +177,6 @@ func TestCreateDir(t *testing.T) {
|
||||
resp, err = c.CreateDir("fooDir", 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did exist, so the creation should have failed."+
|
||||
"The response was: %#v", resp.Key, resp)
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
||||
|
@ -9,44 +9,74 @@ var (
|
||||
ErrWatchStoppedByUser = errors.New("Watch stopped by the user via stop channel")
|
||||
)
|
||||
|
||||
// WatchAll returns the first change under the given prefix since the given index. To
|
||||
// watch for the latest change, set waitIndex = 0.
|
||||
// If recursive is set to true the watch returns the first change under the given
|
||||
// prefix since the given index.
|
||||
//
|
||||
// If the prefix points to a directory, any change under it, including all child directories,
|
||||
// will be returned.
|
||||
// If recursive is set to false the watch returns the first change to the given key
|
||||
// since the given index.
|
||||
//
|
||||
// To watch for the latest change, set waitIndex = 0.
|
||||
//
|
||||
// If a receiver channel is given, it will be a long-term watch. Watch will block at the
|
||||
// channel. And after someone receive the channel, it will go on to watch that prefix.
|
||||
// If a stop channel is given, client can close long-term watch using the stop channel
|
||||
func (c *Client) WatchAll(prefix string, waitIndex uint64, receiver chan *Response, stop chan bool) (*Response, error) {
|
||||
return c.watch(prefix, waitIndex, true, receiver, stop)
|
||||
}
|
||||
|
||||
// Watch returns the first change to the given key since the given index. To
|
||||
// watch for the latest change, set waitIndex = 0.
|
||||
//
|
||||
// If a receiver channel is given, it will be a long-term watch. Watch will block at the
|
||||
// channel. And after someone receive the channel, it will go on to watch that
|
||||
// prefix. If a stop channel is given, client can close long-term watch using
|
||||
// the stop channel
|
||||
func (c *Client) Watch(key string, waitIndex uint64, receiver chan *Response, stop chan bool) (*Response, error) {
|
||||
return c.watch(key, waitIndex, false, receiver, stop)
|
||||
}
|
||||
|
||||
func (c *Client) watch(prefix string, waitIndex uint64, recursive bool, receiver chan *Response, stop chan bool) (*Response, error) {
|
||||
//channel. After someone receives the channel, it will go on to watch that
|
||||
// prefix. If a stop channel is given, the client can close long-term watch using
|
||||
// the stop channel.
|
||||
func (c *Client) Watch(prefix string, waitIndex uint64, recursive bool,
|
||||
receiver chan *Response, stop chan bool) (*Response, error) {
|
||||
logger.Debugf("watch %s [%s]", prefix, c.cluster.Leader)
|
||||
if receiver == nil {
|
||||
return c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
} else {
|
||||
for {
|
||||
resp, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
if resp != nil {
|
||||
waitIndex = resp.ModifiedIndex + 1
|
||||
receiver <- resp
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.toResponse()
|
||||
}
|
||||
|
||||
for {
|
||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := raw.toResponse()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
waitIndex = resp.Node.ModifiedIndex + 1
|
||||
receiver <- resp
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Client) RawWatch(prefix string, waitIndex uint64, recursive bool,
|
||||
receiver chan *RawResponse, stop chan bool) (*RawResponse, error) {
|
||||
|
||||
logger.Debugf("rawWatch %s [%s]", prefix, c.cluster.Leader)
|
||||
if receiver == nil {
|
||||
return c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
}
|
||||
|
||||
for {
|
||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := raw.toResponse()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
waitIndex = resp.Node.ModifiedIndex + 1
|
||||
receiver <- raw
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
@ -54,9 +84,9 @@ func (c *Client) watch(prefix string, waitIndex uint64, recursive bool, receiver
|
||||
|
||||
// helper func
|
||||
// return when there is change under the given prefix
|
||||
func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop chan bool) (*Response, error) {
|
||||
func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop chan bool) (*RawResponse, error) {
|
||||
|
||||
respChan := make(chan *Response)
|
||||
respChan := make(chan *RawResponse, 1)
|
||||
errChan := make(chan error)
|
||||
|
||||
go func() {
|
||||
@ -74,6 +104,7 @@ func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop ch
|
||||
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
respChan <- resp
|
||||
|
@ -9,26 +9,26 @@ import (
|
||||
func TestWatch(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("watch_foo")
|
||||
c.Delete("watch_foo", true)
|
||||
}()
|
||||
|
||||
go setHelper("watch_foo", "bar", c)
|
||||
|
||||
resp, err := c.Watch("watch_foo", 0, nil, nil)
|
||||
resp, err := c.Watch("watch_foo", 0, false, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Key == "/watch_foo" && resp.Value == "bar") {
|
||||
if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("Watch 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
go setHelper("watch_foo", "bar", c)
|
||||
|
||||
resp, err = c.Watch("watch_foo", resp.ModifiedIndex, nil, nil)
|
||||
resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, false, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Key == "/watch_foo" && resp.Value == "bar") {
|
||||
if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("Watch 2 failed: %#v", resp)
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ func TestWatch(t *testing.T) {
|
||||
|
||||
go receiver(ch, stop)
|
||||
|
||||
_, err = c.Watch("watch_foo", 0, ch, stop)
|
||||
_, err = c.Watch("watch_foo", 0, false, ch, stop)
|
||||
if err != ErrWatchStoppedByUser {
|
||||
t.Fatalf("Watch returned a non-user stop error")
|
||||
}
|
||||
@ -48,26 +48,26 @@ func TestWatch(t *testing.T) {
|
||||
func TestWatchAll(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.DeleteAll("watch_foo")
|
||||
c.Delete("watch_foo", true)
|
||||
}()
|
||||
|
||||
go setHelper("watch_foo/foo", "bar", c)
|
||||
|
||||
resp, err := c.WatchAll("watch_foo", 0, nil, nil)
|
||||
resp, err := c.Watch("watch_foo", 0, true, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Key == "/watch_foo/foo" && resp.Value == "bar") {
|
||||
if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("WatchAll 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
go setHelper("watch_foo/foo", "bar", c)
|
||||
|
||||
resp, err = c.WatchAll("watch_foo", resp.ModifiedIndex, nil, nil)
|
||||
resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, true, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Key == "/watch_foo/foo" && resp.Value == "bar") {
|
||||
if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("WatchAll 2 failed: %#v", resp)
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ func TestWatchAll(t *testing.T) {
|
||||
|
||||
go receiver(ch, stop)
|
||||
|
||||
_, err = c.WatchAll("watch_foo", 0, ch, stop)
|
||||
_, err = c.Watch("watch_foo", 0, true, ch, stop)
|
||||
if err != ErrWatchStoppedByUser {
|
||||
t.Fatalf("Watch returned a non-user stop error")
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
context
|
||||
=======
|
||||
[](https://travis-ci.org/gorilla/context)
|
||||
|
||||
gorilla/context is a general purpose registry for global request variables.
|
||||
|
||||
|
@ -92,7 +92,7 @@ func Purge(maxAge int) int {
|
||||
datat = make(map[*http.Request]int64)
|
||||
} else {
|
||||
min := time.Now().Unix() - int64(maxAge)
|
||||
for r, _ := range data {
|
||||
for r := range data {
|
||||
if datat[r] < min {
|
||||
clear(r)
|
||||
count++
|
||||
|
1
third_party/github.com/gorilla/mux/README.md
vendored
1
third_party/github.com/gorilla/mux/README.md
vendored
@ -1,5 +1,6 @@
|
||||
mux
|
||||
===
|
||||
[](https://travis-ci.org/gorilla/mux)
|
||||
|
||||
gorilla/mux is a powerful URL router and dispatcher.
|
||||
|
||||
|
8
third_party/github.com/gorilla/mux/mux.go
vendored
8
third_party/github.com/gorilla/mux/mux.go
vendored
@ -67,6 +67,14 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
||||
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Clean path to canonical form and redirect.
|
||||
if p := cleanPath(req.URL.Path); p != req.URL.Path {
|
||||
|
||||
// Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query.
|
||||
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
|
||||
// http://code.google.com/p/go/issues/detail?id=5252
|
||||
url := *req.URL
|
||||
url.Path = p
|
||||
p = url.String()
|
||||
|
||||
w.Header().Set("Location", p)
|
||||
w.WriteHeader(http.StatusMovedPermanently)
|
||||
return
|
||||
|
53
third_party/github.com/gorilla/mux/mux_test.go
vendored
53
third_party/github.com/gorilla/mux/mux_test.go
vendored
@ -22,6 +22,7 @@ type routeTest struct {
|
||||
shouldMatch bool // whether the request is expected to match the route at all
|
||||
}
|
||||
|
||||
|
||||
func TestHost(t *testing.T) {
|
||||
// newRequestHost a new request with a method, url, and host header
|
||||
newRequestHost := func(method, url, host string) *http.Request {
|
||||
@ -416,6 +417,15 @@ func TestQueries(t *testing.T) {
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Queries route, match with a query string",
|
||||
route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
|
||||
request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Queries route, bad query",
|
||||
route: new(Route).Queries("foo", "bar", "baz", "ding"),
|
||||
@ -663,7 +673,7 @@ func testRoute(t *testing.T, test routeTest) {
|
||||
func TestKeepContext(t *testing.T) {
|
||||
func1 := func(w http.ResponseWriter, r *http.Request) {}
|
||||
|
||||
r := NewRouter()
|
||||
r:= NewRouter()
|
||||
r.HandleFunc("/", func1).Name("func1")
|
||||
|
||||
req, _ := http.NewRequest("GET", "http://localhost/", nil)
|
||||
@ -688,6 +698,47 @@ func TestKeepContext(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
type TestA301ResponseWriter struct {
|
||||
hh http.Header
|
||||
status int
|
||||
}
|
||||
|
||||
func (ho TestA301ResponseWriter) Header() http.Header {
|
||||
return http.Header(ho.hh)
|
||||
}
|
||||
|
||||
func (ho TestA301ResponseWriter) Write( b []byte) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (ho TestA301ResponseWriter) WriteHeader( code int ) {
|
||||
ho.status = code
|
||||
}
|
||||
|
||||
func Test301Redirect(t *testing.T) {
|
||||
m := make(http.Header)
|
||||
|
||||
func1 := func(w http.ResponseWriter, r *http.Request) {}
|
||||
func2 := func(w http.ResponseWriter, r *http.Request) {}
|
||||
|
||||
r:= NewRouter()
|
||||
r.HandleFunc("/api/", func2).Name("func2")
|
||||
r.HandleFunc("/", func1).Name("func1")
|
||||
|
||||
req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
|
||||
|
||||
res := TestA301ResponseWriter{
|
||||
hh: m,
|
||||
status : 0,
|
||||
}
|
||||
r.ServeHTTP(&res, req)
|
||||
|
||||
if "http://localhost/api/?abc=def" != res.hh["Location"][0] {
|
||||
t.Errorf("Should have complete URL with query string")
|
||||
}
|
||||
}
|
||||
|
||||
// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
|
||||
func TestSubrouterHeader(t *testing.T) {
|
||||
expected := "func1 response"
|
||||
|
@ -96,8 +96,8 @@ func TestRouteMatchers(t *testing.T) {
|
||||
method = "GET"
|
||||
headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
|
||||
resultVars = map[bool]map[string]string{
|
||||
true: map[string]string{"var1": "www", "var2": "product", "var3": "42"},
|
||||
false: map[string]string{},
|
||||
true: {"var1": "www", "var2": "product", "var3": "42"},
|
||||
false: {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,8 +110,8 @@ func TestRouteMatchers(t *testing.T) {
|
||||
method = "POST"
|
||||
headers = map[string]string{"Content-Type": "application/json"}
|
||||
resultVars = map[bool]map[string]string{
|
||||
true: map[string]string{"var4": "google", "var5": "product", "var6": "42"},
|
||||
false: map[string]string{},
|
||||
true: {"var4": "google", "var5": "product", "var6": "42"},
|
||||
false: {},
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user