Browse Source

Merge pull request #343 from xiangli-cmu/refactoring_event

Refactoring event
Xiang Li 12 years ago
parent
commit
73f04d5cec
49 changed files with 1007 additions and 688 deletions
  1. 3 3
      server/registry.go
  2. 1 1
      server/v2/tests/delete_handler_test.go
  3. 34 26
      server/v2/tests/get_handler_test.go
  4. 9 5
      server/v2/tests/post_handler_test.go
  5. 19 11
      server/v2/tests/put_handler_test.go
  6. 26 32
      store/event.go
  7. 3 3
      store/event_history.go
  8. 6 6
      store/event_test.go
  9. 1 1
      store/heap_test.go
  10. 0 31
      store/kv_pairs.go
  11. 46 44
      store/node.go
  12. 36 0
      store/node_extern.go
  13. 41 34
      store/store.go
  14. 62 62
      store/store_test.go
  15. 9 9
      store/ttl_key_heap.go
  16. 2 2
      store/watcher_hub.go
  17. 4 4
      store/watcher_test.go
  18. 2 2
      tests/functional/multi_node_kill_all_and_recovery_test.go
  19. 10 10
      tests/functional/remove_node_test.go
  20. 6 4
      tests/functional/simple_multi_node_test.go
  21. 6 4
      tests/functional/simple_snapshot_test.go
  22. 8 5
      tests/functional/single_node_recovery_test.go
  23. 9 6
      tests/functional/single_node_test.go
  24. 1 1
      tests/functional/util.go
  25. 1 1
      tests/functional/v1_migration_test.go
  26. 14 2
      third_party/github.com/coreos/go-etcd/etcd/add_child.go
  27. 8 8
      third_party/github.com/coreos/go-etcd/etcd/add_child_test.go
  28. 35 10
      third_party/github.com/coreos/go-etcd/etcd/client.go
  29. 8 1
      third_party/github.com/coreos/go-etcd/etcd/compare_and_swap.go
  30. 6 6
      third_party/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go
  31. 2 3
      third_party/github.com/coreos/go-etcd/etcd/debug.go
  32. 20 11
      third_party/github.com/coreos/go-etcd/etcd/delete.go
  33. 13 13
      third_party/github.com/coreos/go-etcd/etcd/delete_test.go
  34. 21 17
      third_party/github.com/coreos/go-etcd/etcd/get.go
  35. 46 25
      third_party/github.com/coreos/go-etcd/etcd/get_test.go
  36. 68 0
      third_party/github.com/coreos/go-etcd/etcd/options.go
  37. 118 157
      third_party/github.com/coreos/go-etcd/etcd/requests.go
  38. 47 30
      third_party/github.com/coreos/go-etcd/etcd/response.go
  39. 7 8
      third_party/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go
  40. 81 13
      third_party/github.com/coreos/go-etcd/etcd/set_update_create.go
  41. 27 28
      third_party/github.com/coreos/go-etcd/etcd/set_update_create_test.go
  42. 62 31
      third_party/github.com/coreos/go-etcd/etcd/watch.go
  43. 12 12
      third_party/github.com/coreos/go-etcd/etcd/watch_test.go
  44. 1 0
      third_party/github.com/gorilla/context/README.md
  45. 1 1
      third_party/github.com/gorilla/context/context.go
  46. 1 0
      third_party/github.com/gorilla/mux/README.md
  47. 8 0
      third_party/github.com/gorilla/mux/mux.go
  48. 52 1
      third_party/github.com/gorilla/mux/mux_test.go
  49. 4 4
      third_party/github.com/gorilla/mux/old_test.go

+ 3 - 3
server/registry.go

@@ -70,7 +70,7 @@ func (r *Registry) Count() int {
 	if err != nil {
 	if err != nil {
 		return 0
 		return 0
 	}
 	}
-	return len(e.KVPairs)
+	return len(e.Node.Nodes)
 }
 }
 
 
 // Retrieves the client URL for a given node by name.
 // 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.
 	// Retrieve a list of all nodes.
 	if e, _ := r.store.Get(RegistryKey, false, false); e != nil {
 	if e, _ := r.store.Get(RegistryKey, false, false); e != nil {
 		// Lookup the URL for each one.
 		// Lookup the URL for each one.
-		for _, pair := range e.KVPairs {
+		for _, pair := range e.Node.Nodes {
 			_, name := filepath.Split(pair.Key)
 			_, name := filepath.Split(pair.Key)
 			if url, _ := url(name); len(url) > 0 && name != leaderName {
 			if url, _ := url(name); len(url) > 0 && name != leaderName {
 				urls = append(urls, url)
 				urls = append(urls, url)
@@ -166,7 +166,7 @@ func (r *Registry) load(name string) {
 	}
 	}
 
 
 	// Parse as a query string.
 	// Parse as a query string.
-	m, err := url.ParseQuery(e.Value)
+	m, err := url.ParseQuery(e.Node.Value)
 	if err != nil {
 	if err != nil {
 		panic(fmt.Sprintf("Failed to parse peers entry: %s", name))
 		panic(fmt.Sprintf("Failed to parse peers entry: %s", name))
 	}
 	}

+ 1 - 1
server/v2/tests/delete_handler_test.go

@@ -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{})
 		resp, err = tests.DeleteForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), url.Values{})
 		body := tests.ReadBody(resp)
 		body := tests.ReadBody(resp)
 		assert.Nil(t, err, "")
 		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}}`, "")
 	})
 	})
 }
 }

+ 34 - 26
server/v2/tests/get_handler_test.go

@@ -25,9 +25,11 @@ func TestV2GetKey(t *testing.T) {
 		resp, _ = tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"))
 		resp, _ = tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"))
 		body := tests.ReadBodyJSON(resp)
 		body := tests.ReadBodyJSON(resp)
 		assert.Equal(t, body["action"], "get", "")
 		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"))
 		resp, _ = tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo?recursive=true"))
 		body := tests.ReadBodyJSON(resp)
 		body := tests.ReadBodyJSON(resp)
 		assert.Equal(t, body["action"], "get", "")
 		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, "")
-
-		kv1 := body["kvs"].([]interface{})[1].(map[string]interface{})
-		assert.Equal(t, kv1["key"], "/foo/y", "")
-		assert.Equal(t, kv1["dir"], true, "")
-
-		kvs2 := kv1["kvs"].([]interface{})[0].(map[string]interface{})
-		assert.Equal(t, kvs2["key"], "/foo/y/z", "")
-		assert.Equal(t, kvs2["value"], "YYY", "")
+
+		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, "")
+
+		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, "")
+
+		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.NotNil(t, body, "")
 		assert.Equal(t, body["action"], "set", "")
 		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.NotNil(t, body, "")
 		assert.Equal(t, body["action"], "set", "")
 		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, "")
 	})
 	})
 }
 }

+ 9 - 5
server/v2/tests/post_handler_test.go

@@ -21,18 +21,22 @@ func TestV2CreateUnique(t *testing.T) {
 		resp, _ := tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
 		resp, _ := tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
 		body := tests.ReadBodyJSON(resp)
 		body := tests.ReadBodyJSON(resp)
 		assert.Equal(t, body["action"], "create", "")
 		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.
 		// Second POST should add next index to list.
 		resp, _ = tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
 		resp, _ = tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
 		body = tests.ReadBodyJSON(resp)
 		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.
 		// 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)
 		resp, _ = tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/baz"), nil)
 		body = tests.ReadBodyJSON(resp)
 		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", "")
 	})
 	})
 }
 }

+ 19 - 11
server/v2/tests/put_handler_test.go

@@ -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)
 		resp, err := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		body := tests.ReadBody(resp)
 		body := tests.ReadBody(resp)
 		assert.Nil(t, err, "")
 		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")
 		v.Set("ttl", "20")
 		resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		body := tests.ReadBodyJSON(resp)
 		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.
 		// 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, "")
 		assert.Equal(t, expiration.Sub(t0)/time.Second, 20, "")
 	})
 	})
 }
 }
@@ -74,7 +75,8 @@ func TestV2CreateKeySuccess(t *testing.T) {
 		v.Set("prevExist", "false")
 		v.Set("prevExist", "false")
 		resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		body := tests.ReadBodyJSON(resp)
 		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)
 		resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		body := tests.ReadBodyJSON(resp)
 		body := tests.ReadBodyJSON(resp)
 		assert.Equal(t, body["action"], "update", "")
 		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)
 		resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		body := tests.ReadBodyJSON(resp)
 		body := tests.ReadBodyJSON(resp)
 		assert.Equal(t, body["action"], "compareAndSwap", "")
 		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)
 		resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		body := tests.ReadBodyJSON(resp)
 		body := tests.ReadBodyJSON(resp)
 		assert.Equal(t, body["action"], "compareAndSwap", "")
 		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, "")
 	})
 	})
 }
 }
 
 

+ 26 - 32
store/event.go

@@ -1,9 +1,5 @@
 package store
 package store
 
 
-import (
-	"time"
-)
-
 const (
 const (
 	Get            = "get"
 	Get            = "get"
 	Create         = "create"
 	Create         = "create"
@@ -15,22 +11,20 @@ const (
 )
 )
 
 
 type Event struct {
 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,
 		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
 		return true
 	}
 	}
 
 
-	if e.Action == Set && e.PrevValue == "" {
+	if e.Action == Set && e.Node.PrevValue == "" {
 		return true
 		return true
 	}
 	}
 
 
@@ -47,20 +41,20 @@ func (e *Event) IsCreated() bool {
 }
 }
 
 
 func (e *Event) Index() uint64 {
 func (e *Event) Index() uint64 {
-	return e.ModifiedIndex
+	return e.Node.ModifiedIndex
 }
 }
 
 
 // Converts an event object into a response object.
 // Converts an event object into a response object.
 func (event *Event) Response() interface{} {
 func (event *Event) Response() interface{} {
-	if !event.Dir {
+	if !event.Node.Dir {
 		response := &Response{
 		response := &Response{
 			Action:     event.Action,
 			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 {
 		if response.Action == Set {
@@ -75,15 +69,15 @@ func (event *Event) Response() interface{} {
 
 
 		return response
 		return response
 	} else {
 	} 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{
 			responses[i] = &Response{
 				Action: event.Action,
 				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
 		return responses

+ 3 - 3
store/event_history.go

@@ -34,7 +34,7 @@ func (eh *EventHistory) addEvent(e *Event) *Event {
 
 
 	eh.LastIndex = e.Index()
 	eh.LastIndex = e.Index()
 
 
-	eh.StartIndex = eh.Queue.Events[eh.Queue.Front].ModifiedIndex
+	eh.StartIndex = eh.Queue.Events[eh.Queue.Front].Index()
 
 
 	return e
 	return e
 }
 }
@@ -63,7 +63,7 @@ func (eh *EventHistory) scan(key string, recursive bool, index uint64) (*Event,
 	for {
 	for {
 		e := eh.Queue.Events[i]
 		e := eh.Queue.Events[i]
 
 
-		ok := (e.Key == key)
+		ok := (e.Node.Key == key)
 
 
 		if recursive {
 		if recursive {
 			// add tailing slash
 			// add tailing slash
@@ -72,7 +72,7 @@ func (eh *EventHistory) scan(key string, recursive bool, index uint64) (*Event,
 				key = key + "/"
 				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
 		if ok && index <= e.Index() { // make sure we bypass the smaller one

+ 6 - 6
store/event_test.go

@@ -13,7 +13,7 @@ func TestEventQueue(t *testing.T) {
 
 
 	// Add
 	// Add
 	for i := 0; i < 200; i++ {
 	for i := 0; i < 200; i++ {
-		e := newEvent(Create, "/foo", uint64(i))
+		e := newEvent(Create, "/foo", uint64(i), uint64(i))
 		eh.addEvent(e)
 		eh.addEvent(e)
 	}
 	}
 
 
@@ -35,11 +35,11 @@ func TestScanHistory(t *testing.T) {
 	eh := newEventHistory(100)
 	eh := newEventHistory(100)
 
 
 	// Add
 	// 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)
 	e, err := eh.scan("/foo", false, 1)
 	if err != nil || e.Index() != 1 {
 	if err != nil || e.Index() != 1 {

+ 1 - 1
store/heap_test.go

@@ -33,7 +33,7 @@ func TestHeapPushPop(t *testing.T) {
 func TestHeapUpdate(t *testing.T) {
 func TestHeapUpdate(t *testing.T) {
 	h := newTtlKeyHeap()
 	h := newTtlKeyHeap()
 
 
-	kvs := make([]*Node, 10)
+	kvs := make([]*node, 10)
 
 
 	// add from older expire time to earlier expire time
 	// add from older expire time to earlier expire time
 	// the path is equal to ttl from now
 	// the path is equal to ttl from now

+ 0 - 31
store/kv_pairs.go

@@ -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]
-}

+ 46 - 44
store/node.go

@@ -10,34 +10,34 @@ import (
 
 
 var Permanent time.Time
 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 key-value pair will have a string value
 // A directory will have a children map
 // A directory will have a children map
-type Node struct {
+type node struct {
 	Path string
 	Path string
 
 
-	CreateIndex   uint64
+	CreatedIndex  uint64
 	ModifiedIndex 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
 	ExpireTime time.Time
 	ACL        string
 	ACL        string
 	Value      string           // for key-value pair
 	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.
 	// A reference to the store this node is attached to.
 	store *store
 	store *store
 }
 }
 
 
 // newKV creates a Key-Value pair
 // 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,
 		Path:          nodePath,
-		CreateIndex:   createIndex,
-		ModifiedIndex: createIndex,
+		CreatedIndex:  createdIndex,
+		ModifiedIndex: createdIndex,
 		Parent:        parent,
 		Parent:        parent,
 		ACL:           ACL,
 		ACL:           ACL,
 		store:         store,
 		store:         store,
@@ -47,17 +47,17 @@ func newKV(store *store, nodePath string, value string, createIndex uint64,
 }
 }
 
 
 // newDir creates a directory
 // 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,
 		Path:          nodePath,
-		CreateIndex:   createIndex,
-		ModifiedIndex: createIndex,
+		CreatedIndex:  createdIndex,
+		ModifiedIndex: createdIndex,
 		Parent:        parent,
 		Parent:        parent,
 		ACL:           ACL,
 		ACL:           ACL,
 		ExpireTime:    expireTime,
 		ExpireTime:    expireTime,
-		Children:      make(map[string]*Node),
+		Children:      make(map[string]*node),
 		store:         store,
 		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
 // A hidden node will not be shown via get command under a directory
 // For example if we have /foo/_hidden and /foo/notHidden, get "/foo"
 // For example if we have /foo/_hidden and /foo/notHidden, get "/foo"
 // will only return /foo/notHidden
 // will only return /foo/notHidden
-func (n *Node) IsHidden() bool {
+func (n *node) IsHidden() bool {
 	_, name := path.Split(n.Path)
 	_, name := path.Split(n.Path)
 
 
 	return name[0] == '_'
 	return name[0] == '_'
 }
 }
 
 
 // IsPermanent function checks if the node is a permanent one.
 // 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
 	// we use a uninitialized time.Time to indicate the node is a
 	// permanent one.
 	// permanent one.
 	// the uninitialized time.Time should equal zero.
 	// 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.
 // IsDir function checks whether the node is a directory.
 // If the node is a directory, the function will return true.
 // If the node is a directory, the function will return true.
 // Otherwise the function will return false.
 // Otherwise the function will return false.
-func (n *Node) IsDir() bool {
+func (n *node) IsDir() bool {
 	return !(n.Children == nil)
 	return !(n.Children == nil)
 }
 }
 
 
 // Read function gets the value of the node.
 // 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.
 // 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() {
 	if n.IsDir() {
 		return "", etcdErr.NewError(etcdErr.EcodeNotFile, "", n.store.Index())
 		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.
 // 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.
 // 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() {
 	if n.IsDir() {
 		return etcdErr.NewError(etcdErr.EcodeNotFile, "", n.store.Index())
 		return etcdErr.NewError(etcdErr.EcodeNotFile, "", n.store.Index())
 	}
 	}
@@ -111,7 +111,7 @@ func (n *Node) Write(value string, index uint64) *etcdErr.Error {
 	return nil
 	return nil
 }
 }
 
 
-func (n *Node) ExpirationAndTTL() (*time.Time, int64) {
+func (n *node) ExpirationAndTTL() (*time.Time, int64) {
 	if !n.IsPermanent() {
 	if !n.IsPermanent() {
 		return &n.ExpireTime, int64(n.ExpireTime.Sub(time.Now())/time.Second) + 1
 		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.
 // 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.
 // 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() {
 	if !n.IsDir() {
 		return nil, etcdErr.NewError(etcdErr.EcodeNotDir, "", n.store.Index())
 		return nil, etcdErr.NewError(etcdErr.EcodeNotDir, "", n.store.Index())
 	}
 	}
 
 
-	nodes := make([]*Node, len(n.Children))
+	nodes := make([]*node, len(n.Children))
 
 
 	i := 0
 	i := 0
 	for _, node := range n.Children {
 	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.
 // GetChild function returns the child node under the directory node.
 // On success, it returns the file 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() {
 	if !n.IsDir() {
 		return nil, etcdErr.NewError(etcdErr.EcodeNotDir, n.Path, n.store.Index())
 		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 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"
 // If there is a existing node with the same name under the directory, a "Already Exist"
 // error will be returned
 // error will be returned
-func (n *Node) Add(child *Node) *etcdErr.Error {
+func (n *node) Add(child *node) *etcdErr.Error {
 	if !n.IsDir() {
 	if !n.IsDir() {
 		return etcdErr.NewError(etcdErr.EcodeNotDir, "", n.store.Index())
 		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.
 // 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 {
 	if n.IsDir() && !recursive {
 		// cannot delete a directory without set recursive to true
 		// 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
 	return nil
 }
 }
 
 
-func (n *Node) Pair(recurisive, sorted bool) KeyValuePair {
+func (n *node) Repr(recurisive, sorted bool) NodeExtern {
 	if n.IsDir() {
 	if n.IsDir() {
-		pair := KeyValuePair{
+		node := NodeExtern{
 			Key:           n.Path,
 			Key:           n.Path,
 			Dir:           true,
 			Dir:           true,
 			ModifiedIndex: n.ModifiedIndex,
 			ModifiedIndex: n.ModifiedIndex,
+			CreatedIndex:  n.CreatedIndex,
 		}
 		}
-		pair.Expiration, pair.TTL = n.ExpirationAndTTL()
+		node.Expiration, node.TTL = n.ExpirationAndTTL()
 
 
 		if !recurisive {
 		if !recurisive {
-			return pair
+			return node
 		}
 		}
 
 
 		children, _ := n.List()
 		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 do not use the index in the children slice directly
 		// we need to skip the hidden one
 		// we need to skip the hidden one
@@ -249,30 +250,31 @@ func (n *Node) Pair(recurisive, sorted bool) KeyValuePair {
 				continue
 				continue
 			}
 			}
 
 
-			pair.KVPairs[i] = child.Pair(recurisive, sorted)
+			node.Nodes[i] = child.Repr(recurisive, sorted)
 
 
 			i++
 			i++
 		}
 		}
 
 
 		// eliminate hidden nodes
 		// eliminate hidden nodes
-		pair.KVPairs = pair.KVPairs[:i]
+		node.Nodes = node.Nodes[:i]
 		if sorted {
 		if sorted {
-			sort.Sort(pair.KVPairs)
+			sort.Sort(node.Nodes)
 		}
 		}
 
 
-		return pair
+		return node
 	}
 	}
 
 
-	pair := KeyValuePair{
+	node := NodeExtern{
 		Key:           n.Path,
 		Key:           n.Path,
 		Value:         n.Value,
 		Value:         n.Value,
 		ModifiedIndex: n.ModifiedIndex,
 		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 !n.IsPermanent() {
 		if expireTime.IsZero() {
 		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.
 // 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 directory, it will clone all the content under this directory.
 // If the node is a key-value pair, it will clone the pair.
 // 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() {
 	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 {
 	for key, child := range n.Children {
 		clone.Children[key] = child.Clone()
 		clone.Children[key] = child.Clone()
@@ -320,7 +322,7 @@ func (n *Node) Clone() *Node {
 // call this function on its children.
 // call this function on its children.
 // We check the expire last since we need to recover the whole structure first and add all the
 // We check the expire last since we need to recover the whole structure first and add all the
 // notifications into the event history.
 // notifications into the event history.
-func (n *Node) recoverAndclean() {
+func (n *node) recoverAndclean() {
 	if n.IsDir() {
 	if n.IsDir() {
 		for _, child := range n.Children {
 		for _, child := range n.Children {
 			child.Parent = n
 			child.Parent = n

+ 36 - 0
store/node_extern.go

@@ -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]
+}

+ 41 - 34
store/store.go

@@ -59,7 +59,7 @@ type Store interface {
 }
 }
 
 
 type store struct {
 type store struct {
-	Root           *Node
+	Root           *node
 	WatcherHub     *watcherHub
 	WatcherHub     *watcherHub
 	CurrentIndex   uint64
 	CurrentIndex   uint64
 	Stats          *Stats
 	Stats          *Stats
@@ -113,13 +113,14 @@ func (s *store) Get(nodePath string, recursive, sorted bool) (*Event, error) {
 		return nil, err
 		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
 	if n.IsDir() { // node is a directory
-		e.Dir = true
+		eNode.Dir = true
 
 
 		children, _ := n.List()
 		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 do not use the index in the children slice directly
 		// we need to skip the hidden one
 		// we need to skip the hidden one
@@ -130,29 +131,29 @@ func (s *store) Get(nodePath string, recursive, sorted bool) (*Event, error) {
 				continue
 				continue
 			}
 			}
 
 
-			e.KVPairs[i] = child.Pair(recursive, sorted)
+			eNode.Nodes[i] = child.Repr(recursive, sorted)
 			i++
 			i++
 		}
 		}
 
 
 		// eliminate hidden nodes
 		// eliminate hidden nodes
-		e.KVPairs = e.KVPairs[:i]
+		eNode.Nodes = eNode.Nodes[:i]
 
 
 		if sorted {
 		if sorted {
-			sort.Sort(e.KVPairs)
+			sort.Sort(eNode.Nodes)
 		}
 		}
 
 
 	} else { // node is a file
 	} 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)
 	s.Stats.Inc(GetSuccess)
 
 
 	return e, nil
 	return e, nil
 }
 }
 
 
-// Create function creates the Node at nodePath. Create will help to create intermediate directories with no ttl.
+// Create function creates the node at nodePath. Create will help to create intermediate directories with no ttl.
 // If the node has already existed, create will fail.
 // If the node has already existed, create will fail.
 // If any node on the path is a file, create will fail.
 // If any node on the path is a file, create will fail.
 func (s *store) Create(nodePath string, value string, unique bool, expireTime time.Time) (*Event, error) {
 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
 	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) {
 func (s *store) Set(nodePath string, value string, expireTime time.Time) (*Event, error) {
 	s.worldLock.Lock()
 	s.worldLock.Lock()
 	defer s.worldLock.Unlock()
 	defer s.worldLock.Unlock()
@@ -214,15 +215,17 @@ func (s *store) CompareAndSwap(nodePath string, prevValue string, prevIndex uint
 		// update etcd index
 		// update etcd index
 		s.CurrentIndex++
 		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
 		// if test succeed, write the value
 		n.Write(value, s.CurrentIndex)
 		n.Write(value, s.CurrentIndex)
 		n.UpdateTTL(expireTime)
 		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.WatcherHub.notify(e)
 		s.Stats.Inc(CompareAndSwapSuccess)
 		s.Stats.Inc(CompareAndSwapSuccess)
@@ -255,12 +258,13 @@ func (s *store) Delete(nodePath string, recursive bool) (*Event, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	e := newEvent(Delete, nodePath, nextIndex)
+	e := newEvent(Delete, nodePath, nextIndex, n.CreatedIndex)
+	eNode := e.Node
 
 
 	if n.IsDir() {
 	if n.IsDir() {
-		e.Dir = true
+		eNode.Dir = true
 	} else {
 	} else {
-		e.PrevValue = n.Value
+		eNode.PrevValue = n.Value
 	}
 	}
 
 
 	callback := func(path string) { // notify function
 	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
 // 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, "/")
 	components := strings.Split(nodePath, "/")
 
 
 	curr := s.Root
 	curr := s.Root
@@ -356,7 +360,8 @@ func (s *store) Update(nodePath string, newValue string, expireTime time.Time) (
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	e := newEvent(Update, nodePath, nextIndex)
+	e := newEvent(Update, nodePath, nextIndex, n.CreatedIndex)
+	eNode := e.Node
 
 
 	if len(newValue) != 0 {
 	if len(newValue) != 0 {
 		if n.IsDir() {
 		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)
 			return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, currIndex)
 		}
 		}
 
 
-		e.PrevValue = n.Value
+		eNode.PrevValue = n.Value
 		n.Write(newValue, nextIndex)
 		n.Write(newValue, nextIndex)
-		e.Value = newValue
+		eNode.Value = newValue
+
 	} else {
 	} else {
 		// do not update value
 		// do not update value
-		e.Value = n.Value
+		eNode.Value = n.Value
 	}
 	}
 
 
 	// update ttl
 	// update ttl
 	n.UpdateTTL(expireTime)
 	n.UpdateTTL(expireTime)
 
 
-	e.Expiration, e.TTL = n.ExpirationAndTTL()
+	eNode.Expiration, eNode.TTL = n.ExpirationAndTTL()
 
 
 	s.WatcherHub.notify(e)
 	s.WatcherHub.notify(e)
 
 
@@ -420,7 +426,8 @@ func (s *store) internalCreate(nodePath string, value string, unique bool, repla
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	e := newEvent(action, nodePath, nextIndex)
+	e := newEvent(action, nodePath, nextIndex, nextIndex)
+	eNode := e.Node
 
 
 	n, _ := d.GetChild(newNodeName)
 	n, _ := d.GetChild(newNodeName)
 
 
@@ -430,7 +437,7 @@ func (s *store) internalCreate(nodePath string, value string, unique bool, repla
 			if n.IsDir() {
 			if n.IsDir() {
 				return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, currIndex)
 				return nil, etcdErr.NewError(etcdErr.EcodeNotFile, nodePath, currIndex)
 			}
 			}
-			e.PrevValue, _ = n.Read()
+			eNode.PrevValue, _ = n.Read()
 
 
 			n.Remove(false, nil)
 			n.Remove(false, nil)
 		} else {
 		} else {
@@ -439,12 +446,12 @@ func (s *store) internalCreate(nodePath string, value string, unique bool, repla
 	}
 	}
 
 
 	if len(value) != 0 { // create file
 	if len(value) != 0 { // create file
-		e.Value = value
+		eNode.Value = value
 
 
 		n = newKV(s, nodePath, value, nextIndex, d, "", expireTime)
 		n = newKV(s, nodePath, value, nextIndex, d, "", expireTime)
 
 
 	} else { // create directory
 	} else { // create directory
-		e.Dir = true
+		eNode.Dir = true
 
 
 		n = newDir(s, nodePath, nextIndex, d, "", expireTime)
 		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
 	// we are sure d is a directory and does not have the children with name n.Name
 	d.Add(n)
 	d.Add(n)
 
 
-	// Node with TTL
+	// node with TTL
 	if !n.IsPermanent() {
 	if !n.IsPermanent() {
 		s.ttlKeyHeap.push(n)
 		s.ttlKeyHeap.push(n)
 
 
-		e.Expiration, e.TTL = n.ExpirationAndTTL()
+		eNode.Expiration, eNode.TTL = n.ExpirationAndTTL()
 	}
 	}
 
 
 	s.CurrentIndex = nextIndex
 	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.
 // 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))
 	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() {
 		if !parent.IsDir() {
 			err := etcdErr.NewError(etcdErr.EcodeNotDir, parent.Path, s.CurrentIndex)
 			err := etcdErr.NewError(etcdErr.EcodeNotDir, parent.Path, s.CurrentIndex)
@@ -510,7 +517,7 @@ func (s *store) DeleteExpiredKeys(cutoff time.Time) {
 		s.CurrentIndex++
 		s.CurrentIndex++
 
 
 		s.Stats.Inc(ExpireCount)
 		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 is a directory, this function will return the pointer to that node.
 // If it does not exist, this function will create a new directory and return the pointer to that node.
 // If it does not exist, this function will create a new directory and return the pointer to that node.
 // If it is a file, this function will return error.
 // If it is a file, this function will return error.
-func (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]
 	node, ok := parent.Children[dirName]
 
 
 	if ok {
 	if ok {

+ 62 - 62
store/store_test.go

@@ -31,8 +31,8 @@ func TestStoreGetValue(t *testing.T) {
 	e, err := s.Get("/foo", false, false)
 	e, err := s.Get("/foo", false, false)
 	assert.Nil(t, err, "")
 	assert.Nil(t, err, "")
 	assert.Equal(t, e.Action, "get", "")
 	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.
 // 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)
 	e, err := s.Get("/foo", true, false)
 	assert.Nil(t, err, "")
 	assert.Nil(t, err, "")
 	assert.Equal(t, e.Action, "get", "")
 	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.
 // 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)
 	s.Create("/foo/y/b", "0", false, Permanent)
 	e, err := s.Get("/foo", true, true)
 	e, err := s.Get("/foo", true, true)
 	assert.Nil(t, err, "")
 	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.
 // 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)
 	e, err := s.Create("/foo", "bar", false, Permanent)
 	assert.Nil(t, err, "")
 	assert.Nil(t, err, "")
 	assert.Equal(t, e.Action, "create", "")
 	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.
 // 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)
 	e, err := s.Create("/foo", "", false, Permanent)
 	assert.Nil(t, err, "")
 	assert.Nil(t, err, "")
 	assert.Equal(t, e.Action, "create", "")
 	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.
 // 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)
 	e, err := s.Update("/foo", "baz", Permanent)
 	assert.Nil(t, err, "")
 	assert.Nil(t, err, "")
 	assert.Equal(t, e.Action, "update", "")
 	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)
 	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.
 // Ensure that the store cannot update a directory.
@@ -165,7 +165,7 @@ func TestStoreUpdateValueTTL(t *testing.T) {
 	s.Create("/foo", "bar", false, Permanent)
 	s.Create("/foo", "bar", false, Permanent)
 	_, err := s.Update("/foo", "baz", time.Now().Add(500*time.Millisecond))
 	_, err := s.Update("/foo", "baz", time.Now().Add(500*time.Millisecond))
 	e, _ := s.Get("/foo", false, false)
 	e, _ := s.Get("/foo", false, false)
-	assert.Equal(t, e.Value, "baz", "")
+	assert.Equal(t, e.Node.Value, "baz", "")
 
 
 	time.Sleep(600 * time.Millisecond)
 	time.Sleep(600 * time.Millisecond)
 	e, err = s.Get("/foo", false, false)
 	e, err = s.Get("/foo", false, false)
@@ -187,7 +187,7 @@ func TestStoreUpdateDirTTL(t *testing.T) {
 	s.Create("/foo/bar", "baz", false, Permanent)
 	s.Create("/foo/bar", "baz", false, Permanent)
 	_, err := s.Update("/foo", "", time.Now().Add(500*time.Millisecond))
 	_, err := s.Update("/foo", "", time.Now().Add(500*time.Millisecond))
 	e, _ := s.Get("/foo/bar", false, false)
 	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)
 	time.Sleep(600 * time.Millisecond)
 	e, err = s.Get("/foo/bar", false, false)
 	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)
 	e, err := s.CompareAndSwap("/foo", "bar", 0, "baz", Permanent)
 	assert.Nil(t, err, "")
 	assert.Nil(t, err, "")
 	assert.Equal(t, e.Action, "compareAndSwap", "")
 	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)
 	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.
 // 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.Equal(t, err.Message, "Test Failed", "")
 	assert.Nil(t, e, "")
 	assert.Nil(t, e, "")
 	e, _ = s.Get("/foo", false, false)
 	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.
 // 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)
 	e, err := s.CompareAndSwap("/foo", "", 1, "baz", Permanent)
 	assert.Nil(t, err, "")
 	assert.Nil(t, err, "")
 	assert.Equal(t, e.Action, "compareAndSwap", "")
 	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)
 	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.
 // 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.Equal(t, err.Message, "Test Failed", "")
 	assert.Nil(t, e, "")
 	assert.Nil(t, e, "")
 	e, _ = s.Get("/foo", false, false)
 	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.
 // Ensure that the store can watch for key creation.
@@ -303,7 +303,7 @@ func TestStoreWatchCreate(t *testing.T) {
 	s.Create("/foo", "bar", false, Permanent)
 	s.Create("/foo", "bar", false, Permanent)
 	e := nbselect(c)
 	e := nbselect(c)
 	assert.Equal(t, e.Action, "create", "")
 	assert.Equal(t, e.Action, "create", "")
-	assert.Equal(t, e.Key, "/foo", "")
+	assert.Equal(t, e.Node.Key, "/foo", "")
 	e = nbselect(c)
 	e = nbselect(c)
 	assert.Nil(t, e, "")
 	assert.Nil(t, e, "")
 }
 }
@@ -315,7 +315,7 @@ func TestStoreWatchRecursiveCreate(t *testing.T) {
 	s.Create("/foo/bar", "baz", false, Permanent)
 	s.Create("/foo/bar", "baz", false, Permanent)
 	e := nbselect(c)
 	e := nbselect(c)
 	assert.Equal(t, e.Action, "create", "")
 	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.
 // Ensure that the store can watch for key updates.
@@ -326,7 +326,7 @@ func TestStoreWatchUpdate(t *testing.T) {
 	s.Update("/foo", "baz", Permanent)
 	s.Update("/foo", "baz", Permanent)
 	e := nbselect(c)
 	e := nbselect(c)
 	assert.Equal(t, e.Action, "update", "")
 	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.
 // 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)
 	s.Update("/foo/bar", "baz", Permanent)
 	e := nbselect(c)
 	e := nbselect(c)
 	assert.Equal(t, e.Action, "update", "")
 	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.
 // Ensure that the store can watch for key deletions.
@@ -348,7 +348,7 @@ func TestStoreWatchDelete(t *testing.T) {
 	s.Delete("/foo", false)
 	s.Delete("/foo", false)
 	e := nbselect(c)
 	e := nbselect(c)
 	assert.Equal(t, e.Action, "delete", "")
 	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.
 // Ensure that the store can watch for recursive key deletions.
@@ -359,7 +359,7 @@ func TestStoreWatchRecursiveDelete(t *testing.T) {
 	s.Delete("/foo/bar", false)
 	s.Delete("/foo/bar", false)
 	e := nbselect(c)
 	e := nbselect(c)
 	assert.Equal(t, e.Action, "delete", "")
 	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.
 // 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)
 	s.CompareAndSwap("/foo", "bar", 0, "baz", Permanent)
 	e := nbselect(c)
 	e := nbselect(c)
 	assert.Equal(t, e.Action, "compareAndSwap", "")
 	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.
 // 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)
 	s.CompareAndSwap("/foo/bar", "baz", 0, "bat", Permanent)
 	e := nbselect(c)
 	e := nbselect(c)
 	assert.Equal(t, e.Action, "compareAndSwap", "")
 	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.
 // Ensure that the store can watch for key expiration.
@@ -403,11 +403,11 @@ func TestStoreWatchExpire(t *testing.T) {
 	time.Sleep(600 * time.Millisecond)
 	time.Sleep(600 * time.Millisecond)
 	e = nbselect(c)
 	e = nbselect(c)
 	assert.Equal(t, e.Action, "expire", "")
 	assert.Equal(t, e.Action, "expire", "")
-	assert.Equal(t, e.Key, "/foo", "")
+	assert.Equal(t, e.Node.Key, "/foo", "")
 	c, _ = s.Watch("/", true, 4)
 	c, _ = s.Watch("/", true, 4)
 	e = nbselect(c)
 	e = nbselect(c)
 	assert.Equal(t, e.Action, "expire", "")
 	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.
 // 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)
 	e, err := s.Get("/foo/x", false, false)
 	assert.Nil(t, err, "")
 	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)
 	e, err = s.Get("/foo/y", false, false)
 	assert.Nil(t, err, "")
 	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.
 // 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)
 	e, err := s.Get("/foo/x", false, false)
 	assert.Nil(t, err, "")
 	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)
 	e, err = s.Get("/foo/y", false, false)
 	assert.NotNil(t, err, "")
 	assert.NotNil(t, err, "")

+ 9 - 9
store/ttl_key_heap.go

@@ -6,12 +6,12 @@ import (
 
 
 // An TTLKeyHeap is a min-heap of TTLKeys order by expiration time
 // An TTLKeyHeap is a min-heap of TTLKeys order by expiration time
 type ttlKeyHeap struct {
 type ttlKeyHeap struct {
-	array  []*Node
-	keyMap map[*Node]int
+	array  []*node
+	keyMap map[*node]int
 }
 }
 
 
 func newTtlKeyHeap() *ttlKeyHeap {
 func newTtlKeyHeap() *ttlKeyHeap {
-	h := &ttlKeyHeap{keyMap: make(map[*Node]int)}
+	h := &ttlKeyHeap{keyMap: make(map[*node]int)}
 	heap.Init(h)
 	heap.Init(h)
 	return h
 	return h
 }
 }
@@ -34,7 +34,7 @@ func (h ttlKeyHeap) Swap(i, j int) {
 }
 }
 
 
 func (h *ttlKeyHeap) Push(x interface{}) {
 func (h *ttlKeyHeap) Push(x interface{}) {
-	n, _ := x.(*Node)
+	n, _ := x.(*node)
 	h.keyMap[n] = len(h.array)
 	h.keyMap[n] = len(h.array)
 	h.array = append(h.array, n)
 	h.array = append(h.array, n)
 }
 }
@@ -48,16 +48,16 @@ func (h *ttlKeyHeap) Pop() interface{} {
 	return x
 	return x
 }
 }
 
 
-func (h *ttlKeyHeap) top() *Node {
+func (h *ttlKeyHeap) top() *node {
 	if h.Len() != 0 {
 	if h.Len() != 0 {
 		return h.array[0]
 		return h.array[0]
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-func (h *ttlKeyHeap) pop() *Node {
+func (h *ttlKeyHeap) pop() *node {
 	x := heap.Pop(h)
 	x := heap.Pop(h)
-	n, _ := x.(*Node)
+	n, _ := x.(*node)
 	return n
 	return n
 }
 }
 
 
@@ -65,7 +65,7 @@ func (h *ttlKeyHeap) push(x interface{}) {
 	heap.Push(h, x)
 	heap.Push(h, x)
 }
 }
 
 
-func (h *ttlKeyHeap) update(n *Node) {
+func (h *ttlKeyHeap) update(n *node) {
 	index, ok := h.keyMap[n]
 	index, ok := h.keyMap[n]
 	if ok {
 	if ok {
 		heap.Remove(h, index)
 		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]
 	index, ok := h.keyMap[n]
 	if ok {
 	if ok {
 		heap.Remove(h, index)
 		heap.Remove(h, index)

+ 2 - 2
store/watcher_hub.go

@@ -77,7 +77,7 @@ func (wh *watcherHub) watch(key string, recursive bool, index uint64) (<-chan *E
 func (wh *watcherHub) notify(e *Event) {
 func (wh *watcherHub) notify(e *Event) {
 	e = wh.EventHistory.addEvent(e) // add event into the eventHistory
 	e = wh.EventHistory.addEvent(e) // add event into the eventHistory
 
 
-	segments := strings.Split(e.Key, "/")
+	segments := strings.Split(e.Node.Key, "/")
 
 
 	currPath := "/"
 	currPath := "/"
 
 
@@ -111,7 +111,7 @@ func (wh *watcherHub) notifyWatchers(e *Event, path string, deleted bool) {
 
 
 			w, _ := curr.Value.(*watcher)
 			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
 				// if we successfully notify a watcher
 				// we need to remove the watcher from the list
 				// we need to remove the watcher from the list

+ 4 - 4
store/watcher_test.go

@@ -35,7 +35,7 @@ func TestWatcher(t *testing.T) {
 		// do nothing
 		// do nothing
 	}
 	}
 
 
-	e := newEvent(Create, "/foo/bar", 1)
+	e := newEvent(Create, "/foo/bar", 1, 1)
 
 
 	wh.notify(e)
 	wh.notify(e)
 
 
@@ -47,7 +47,7 @@ func TestWatcher(t *testing.T) {
 
 
 	c, _ = wh.watch("/foo", false, 2)
 	c, _ = wh.watch("/foo", false, 2)
 
 
-	e = newEvent(Create, "/foo/bar", 2)
+	e = newEvent(Create, "/foo/bar", 2, 2)
 
 
 	wh.notify(e)
 	wh.notify(e)
 
 
@@ -58,7 +58,7 @@ func TestWatcher(t *testing.T) {
 		// do nothing
 		// do nothing
 	}
 	}
 
 
-	e = newEvent(Create, "/foo", 3)
+	e = newEvent(Create, "/foo", 3, 3)
 
 
 	wh.notify(e)
 	wh.notify(e)
 
 
@@ -78,7 +78,7 @@ func TestWatcher(t *testing.T) {
 		// do nothing
 		// do nothing
 	}
 	}
 
 
-	e = newEvent(Create, "/fo/bar", 3)
+	e = newEvent(Create, "/fo/bar", 3, 3)
 
 
 	wh.notify(e)
 	wh.notify(e)
 
 

+ 2 - 2
tests/functional/multi_node_kill_all_and_recovery_test.go

@@ -65,7 +65,7 @@ func TestMultiNodeKillAllAndRecovery(t *testing.T) {
 		t.Fatalf("Recovery error: %s", err)
 		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)
 	}
 	}
 }
 }

+ 10 - 10
tests/functional/remove_node_test.go

@@ -35,13 +35,13 @@ func TestRemoveNode(t *testing.T) {
 			fmt.Println("send remove to node3 and wait for its exiting")
 			fmt.Println("send remove to node3 and wait for its exiting")
 			etcds[2].Wait()
 			etcds[2].Wait()
 
 
-			resp, err := c.Get("_etcd/machines", false)
+			resp, err := c.Get("_etcd/machines", false, false)
 
 
 			if err != nil {
 			if err != nil {
 				panic(err)
 				panic(err)
 			}
 			}
 
 
-			if len(resp.Kvs) != 2 {
+			if len(resp.Node.Nodes) != 2 {
 				t.Fatal("cannot remove peer")
 				t.Fatal("cannot remove peer")
 			}
 			}
 
 
@@ -59,14 +59,14 @@ func TestRemoveNode(t *testing.T) {
 
 
 			time.Sleep(time.Second)
 			time.Sleep(time.Second)
 
 
-			resp, err = c.Get("_etcd/machines", false)
+			resp, err = c.Get("_etcd/machines", false, false)
 
 
 			if err != nil {
 			if err != nil {
 				panic(err)
 				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)
 			client.Do(rmReq)
 
 
-			resp, err := c.Get("_etcd/machines", false)
+			resp, err := c.Get("_etcd/machines", false, false)
 
 
 			if err != nil {
 			if err != nil {
 				panic(err)
 				panic(err)
 			}
 			}
 
 
-			if len(resp.Kvs) != 2 {
+			if len(resp.Node.Nodes) != 2 {
 				t.Fatal("cannot remove peer")
 				t.Fatal("cannot remove peer")
 			}
 			}
 
 
@@ -102,14 +102,14 @@ func TestRemoveNode(t *testing.T) {
 
 
 			time.Sleep(time.Second)
 			time.Sleep(time.Second)
 
 
-			resp, err = c.Get("_etcd/machines", false)
+			resp, err = c.Get("_etcd/machines", false, false)
 
 
 			if err != nil {
 			if err != nil {
 				panic(err)
 				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))
 			}
 			}
 		}
 		}
 	}
 	}

+ 6 - 4
tests/functional/simple_multi_node_test.go

@@ -39,24 +39,26 @@ func templateTestSimpleMultiNode(t *testing.T, tls bool) {
 
 
 	// Test Set
 	// Test Set
 	result, err := c.Set("foo", "bar", 100)
 	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 {
 		if err != nil {
 			t.Fatal(err)
 			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)
 	time.Sleep(time.Second)
 
 
 	result, err = c.Set("foo", "bar", 100)
 	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 {
 		if err != nil {
 			t.Fatal(err)
 			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)
 	}
 	}
 
 
 }
 }

+ 6 - 4
tests/functional/simple_snapshot_test.go

@@ -30,13 +30,14 @@ func TestSimpleSnapshot(t *testing.T) {
 	// issue first 501 commands
 	// issue first 501 commands
 	for i := 0; i < 501; i++ {
 	for i := 0; i < 501; i++ {
 		result, err := c.Set("foo", "bar", 100)
 		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 {
 			if err != nil {
 				t.Fatal(err)
 				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
 	// issue second 501 commands
 	for i := 0; i < 501; i++ {
 	for i := 0; i < 501; i++ {
 		result, err := c.Set("foo", "bar", 100)
 		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 {
 			if err != nil {
 				t.Fatal(err)
 				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)
 		}
 		}
 	}
 	}
 
 

+ 8 - 5
tests/functional/single_node_recovery_test.go

@@ -28,13 +28,14 @@ func TestSingleNodeRecovery(t *testing.T) {
 	c.SyncCluster()
 	c.SyncCluster()
 	// Test Set
 	// Test Set
 	result, err := c.Set("foo", "bar", 100)
 	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 {
 		if err != nil {
 			t.Fatal(err)
 			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)
 	time.Sleep(time.Second)
@@ -50,16 +51,18 @@ func TestSingleNodeRecovery(t *testing.T) {
 
 
 	time.Sleep(time.Second)
 	time.Sleep(time.Second)
 
 
-	result, err = c.Get("foo", false)
+	result, err = c.Get("foo", false, false)
+	node = result.Node
+
 	if err != nil {
 	if err != nil {
 		t.Fatal("get fail: " + err.Error())
 		t.Fatal("get fail: " + err.Error())
 		return
 		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 {
 		if err != nil {
 			t.Fatal(err)
 			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)
 	}
 	}
 }
 }

+ 9 - 6
tests/functional/single_node_test.go

@@ -28,36 +28,39 @@ func TestSingleNode(t *testing.T) {
 	c.SyncCluster()
 	c.SyncCluster()
 	// Test Set
 	// Test Set
 	result, err := c.Set("foo", "bar", 100)
 	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 {
 		if err != nil {
 			t.Fatal("Set 1: ", err)
 			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)
 	time.Sleep(time.Second)
 
 
 	result, err = c.Set("foo", "bar", 100)
 	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 {
 		if err != nil {
 			t.Fatal("Set 2: ", err)
 			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
 	// Add a test-and-set test
 
 
 	// First, we'll test we can change the value if we get it write
 	// First, we'll test we can change the value if we get it write
 	result, err = c.CompareAndSwap("foo", "foobar", 100, "bar", 0)
 	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 {
 		if err != nil {
 			t.Fatal(err)
 			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
 	// Next, we'll make sure we can't set it without the correct prior value

+ 1 - 1
tests/functional/util.go

@@ -44,7 +44,7 @@ func Set(stop chan bool) {
 
 
 		result, err := c.Set(key, "bar", 0)
 		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 {
 			select {
 			case <-stop:
 			case <-stop:
 				stopSet = true
 				stopSet = true

+ 1 - 1
tests/functional/v1_migration_test.go

@@ -99,5 +99,5 @@ func TestV1ClusterMigration(t *testing.T) {
 	body = tests.ReadBody(resp)
 	body = tests.ReadBody(resp)
 	assert.Nil(t, err, "")
 	assert.Nil(t, err, "")
 	assert.Equal(t, resp.StatusCode, 200, "")
 	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}}`)
 }
 }

+ 14 - 2
third_party/github.com/coreos/go-etcd/etcd/add_child.go

@@ -2,10 +2,22 @@ package etcd
 
 
 // Add a new directory with a random etcd-generated key under the given path.
 // Add a new directory with a random etcd-generated key under the given path.
 func (c *Client) AddChildDir(key string, ttl uint64) (*Response, error) {
 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.
 // 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) {
 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()
 }
 }

+ 8 - 8
third_party/github.com/coreos/go-etcd/etcd/add_child_test.go

@@ -5,8 +5,8 @@ import "testing"
 func TestAddChild(t *testing.T) {
 func TestAddChild(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("fooDir")
-		c.DeleteAll("nonexistentDir")
+		c.Delete("fooDir", true)
+		c.Delete("nonexistentDir", true)
 	}()
 	}()
 
 
 	c.SetDir("fooDir", 5)
 	c.SetDir("fooDir", 5)
@@ -21,10 +21,10 @@ func TestAddChild(t *testing.T) {
 		t.Fatal(err)
 		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
 	// The child with v0 should proceed the child with v1 because it's added
 	// earlier, so it should have a lower key.
 	// 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."+
 		t.Fatalf("AddChild 1 failed.  There should be two chlidren whose values are v0 and v1, respectively."+
 			"  The response was: %#v", resp)
 			"  The response was: %#v", resp)
 	}
 	}
@@ -40,8 +40,8 @@ func TestAddChild(t *testing.T) {
 func TestAddChildDir(t *testing.T) {
 func TestAddChildDir(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("fooDir")
-		c.DeleteAll("nonexistentDir")
+		c.Delete("fooDir", true)
+		c.Delete("nonexistentDir", true)
 	}()
 	}()
 
 
 	c.SetDir("fooDir", 5)
 	c.SetDir("fooDir", 5)
@@ -56,10 +56,10 @@ func TestAddChildDir(t *testing.T) {
 		t.Fatal(err)
 		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
 	// The child with v0 should proceed the child with v1 because it's added
 	// earlier, so it should have a lower key.
 	// 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."+
 		t.Fatalf("AddChildDir 1 failed.  There should be two chlidren whose values are v0 and v1, respectively."+
 			"  The response was: %#v", resp)
 			"  The response was: %#v", resp)
 	}
 	}

+ 35 - 10
third_party/github.com/coreos/go-etcd/etcd/client.go

@@ -11,7 +11,6 @@ import (
 	"net/url"
 	"net/url"
 	"os"
 	"os"
 	"path"
 	"path"
-	"reflect"
 	"strings"
 	"strings"
 	"time"
 	"time"
 )
 )
@@ -30,6 +29,10 @@ const (
 	WEAK_CONSISTENCY   = "WEAK"
 	WEAK_CONSISTENCY   = "WEAK"
 )
 )
 
 
+const (
+	defaultBufferSize = 10
+)
+
 type Cluster struct {
 type Cluster struct {
 	Leader   string   `json:"leader"`
 	Leader   string   `json:"leader"`
 	Machines []string `json:"machines"`
 	Machines []string `json:"machines"`
@@ -48,14 +51,9 @@ type Client struct {
 	config      Config  `json:"config"`
 	config      Config  `json:"config"`
 	httpClient  *http.Client
 	httpClient  *http.Client
 	persistence io.Writer
 	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
 // NewClient create a basic client that is configured to be used
 // with the given machine list.
 // with the given machine list.
 func NewClient(machines []string) *Client {
 func NewClient(machines []string) *Client {
@@ -333,9 +331,7 @@ func dialTimeout(network, addr string) (net.Conn, error) {
 	return net.DialTimeout(network, addr, time.Second)
 	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
 	var leader string
 	if u.Scheme == "" {
 	if u.Scheme == "" {
 		leader = "http://" + u.Host
 		leader = "http://" + u.Host
@@ -347,3 +343,32 @@ func (c *Client) updateLeader(httpPath string) {
 	c.cluster.Leader = leader
 	c.cluster.Leader = leader
 	c.saveConfig()
 	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
+}

+ 8 - 1
third_party/github.com/coreos/go-etcd/etcd/compare_and_swap.go

@@ -14,5 +14,12 @@ func (c *Client) CompareAndSwap(key string, value string, ttl uint64, prevValue
 	if prevIndex != 0 {
 	if prevIndex != 0 {
 		options["prevIndex"] = prevIndex
 		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()
 }
 }

+ 6 - 6
third_party/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go

@@ -7,7 +7,7 @@ import (
 func TestCompareAndSwap(t *testing.T) {
 func TestCompareAndSwap(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("foo")
+		c.Delete("foo", true)
 	}()
 	}()
 
 
 	c.Set("foo", "bar", 5)
 	c.Set("foo", "bar", 5)
@@ -17,8 +17,8 @@ func TestCompareAndSwap(t *testing.T) {
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		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)
 		t.Fatalf("CompareAndSwap 1 failed: %#v", resp)
 	}
 	}
 
 
@@ -34,12 +34,12 @@ func TestCompareAndSwap(t *testing.T) {
 	}
 	}
 
 
 	// This should succeed
 	// 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 {
 	if err != nil {
 		t.Fatal(err)
 		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)
 		t.Fatalf("CompareAndSwap 1 failed: %#v", resp)
 	}
 	}
 
 

+ 2 - 3
third_party/github.com/coreos/go-etcd/etcd/debug.go

@@ -1,16 +1,15 @@
 package etcd
 package etcd
 
 
 import (
 import (
-	"github.com/coreos/go-log/log"
 	"os"
 	"os"
+
+	"github.com/coreos/go-log/log"
 )
 )
 
 
 var logger *log.Logger
 var logger *log.Logger
 
 
 func init() {
 func init() {
 	setLogger(log.PriErr)
 	setLogger(log.PriErr)
-	// Uncomment the following line if you want to see lots of logs
-	// OpenDebug()
 }
 }
 
 
 func OpenDebug() {
 func OpenDebug() {

+ 20 - 11
third_party/github.com/coreos/go-etcd/etcd/delete.go

@@ -1,17 +1,26 @@
 package etcd
 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.
 // 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)
 }
 }

+ 13 - 13
third_party/github.com/coreos/go-etcd/etcd/delete_test.go

@@ -7,21 +7,21 @@ import (
 func TestDelete(t *testing.T) {
 func TestDelete(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("foo")
+		c.Delete("foo", true)
 	}()
 	}()
 
 
 	c.Set("foo", "bar", 5)
 	c.Set("foo", "bar", 5)
-	resp, err := c.Delete("foo")
+	resp, err := c.Delete("foo", false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		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 {
 	if err == nil {
 		t.Fatalf("Delete should have failed because the key foo did not exist.  "+
 		t.Fatalf("Delete should have failed because the key foo did not exist.  "+
 			"The response was: %v", resp)
 			"The response was: %v", resp)
@@ -31,32 +31,32 @@ func TestDelete(t *testing.T) {
 func TestDeleteAll(t *testing.T) {
 func TestDeleteAll(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("foo")
-		c.DeleteAll("fooDir")
+		c.Delete("foo", true)
+		c.Delete("fooDir", true)
 	}()
 	}()
 
 
 	c.Set("foo", "bar", 5)
 	c.Set("foo", "bar", 5)
-	resp, err := c.DeleteAll("foo")
+	resp, err := c.Delete("foo", true)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	if !(resp.PrevValue == "bar" && resp.Value == "") {
+	if !(resp.Node.PrevValue == "bar" && resp.Node.Value == "") {
 		t.Fatalf("DeleteAll 1 failed: %#v", resp)
 		t.Fatalf("DeleteAll 1 failed: %#v", resp)
 	}
 	}
 
 
 	c.SetDir("fooDir", 5)
 	c.SetDir("fooDir", 5)
 	c.Set("fooDir/foo", "bar", 5)
 	c.Set("fooDir/foo", "bar", 5)
-	resp, err = c.DeleteAll("fooDir")
+	resp, err = c.Delete("fooDir", true)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	if !(resp.PrevValue == "" && resp.Value == "") {
+	if !(resp.Node.PrevValue == "" && resp.Node.Value == "") {
 		t.Fatalf("DeleteAll 2 failed: %#v", resp)
 		t.Fatalf("DeleteAll 2 failed: %#v", resp)
 	}
 	}
 
 
-	resp, err = c.DeleteAll("foo")
+	resp, err = c.Delete("foo", true)
 	if err == nil {
 	if err == nil {
 		t.Fatalf("DeleteAll should have failed because the key foo did not exist.  "+
 		t.Fatalf("DeleteAll should have failed because the key foo did not exist.  "+
 			"The response was: %v", resp)
 			"The response was: %v", resp)

+ 21 - 17
third_party/github.com/coreos/go-etcd/etcd/get.go

@@ -1,23 +1,27 @@
 package etcd
 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.
 // Get gets the file or directory associated with the given key.
 // If the key points to a directory, files and directories under
 // If the key points to a directory, files and directories under
 // it will be returned in sorted or unsorted order, depending on
 // 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)
 }
 }

+ 46 - 25
third_party/github.com/coreos/go-etcd/etcd/get_test.go

@@ -8,22 +8,22 @@ import (
 func TestGet(t *testing.T) {
 func TestGet(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("foo")
+		c.Delete("foo", true)
 	}()
 	}()
 
 
 	c.Set("foo", "bar", 5)
 	c.Set("foo", "bar", 5)
 
 
-	result, err := c.Get("foo", false)
+	result, err := c.Get("foo", false, false)
 
 
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		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 {
 	if err == nil {
 		t.Fatalf("should not be able to get non-exist key")
 		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) {
 func TestGetAll(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("fooDir")
+		c.Delete("fooDir", true)
 	}()
 	}()
 
 
 	c.SetDir("fooDir", 5)
 	c.SetDir("fooDir", 5)
@@ -40,25 +40,36 @@ func TestGetAll(t *testing.T) {
 	c.Set("fooDir/k1", "v1", 5)
 	c.Set("fooDir/k1", "v1", 5)
 
 
 	// Return kv-pairs in sorted order
 	// Return kv-pairs in sorted order
-	result, err := c.Get("fooDir", true)
+	result, err := c.Get("fooDir", true, false)
 
 
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		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
 	// Test the `recursive` option
@@ -66,34 +77,44 @@ func TestGetAll(t *testing.T) {
 	c.Set("fooDir/childDir/k2", "v2", 5)
 	c.Set("fooDir/childDir/k2", "v2", 5)
 
 
 	// Return kv-pairs in sorted order
 	// 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 {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	expected = kvPairs{
-		KeyValuePair{
+	expected = Nodes{
+		Node{
 			Key: "/fooDir/childDir",
 			Key: "/fooDir/childDir",
 			Dir: true,
 			Dir: true,
-			KVPairs: kvPairs{
-				KeyValuePair{
+			Nodes: Nodes{
+				Node{
 					Key:   "/fooDir/childDir/k2",
 					Key:   "/fooDir/childDir/k2",
 					Value: "v2",
 					Value: "v2",
+					TTL:   5,
 				},
 				},
 			},
 			},
+			TTL: 5,
 		},
 		},
-		KeyValuePair{
+		Node{
 			Key:   "/fooDir/k0",
 			Key:   "/fooDir/k0",
 			Value: "v0",
 			Value: "v0",
+			TTL:   5,
 		},
 		},
-		KeyValuePair{
+		Node{
 			Key:   "/fooDir/k1",
 			Key:   "/fooDir/k1",
 			Value: "v1",
 			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 - 0
third_party/github.com/coreos/go-etcd/etcd/options.go

@@ -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
+}

+ 118 - 157
third_party/github.com/coreos/go-etcd/etcd/requests.go

@@ -1,54 +1,18 @@
 package etcd
 package etcd
 
 
 import (
 import (
-	"encoding/json"
-	"errors"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
 	"math/rand"
 	"math/rand"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
 	"path"
 	"path"
-	"reflect"
 	"strings"
 	"strings"
 	"time"
 	"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
 // 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)
 	logger.Debugf("get %s [%s]", key, c.cluster.Leader)
 
 
 	p := path.Join("keys", key)
 	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 {
 	if c.config.Consistency == STRONG_CONSISTENCY {
 		options["consistent"] = true
 		options["consistent"] = true
 	}
 	}
-	if options != nil {
-		str, err := optionsToString(options, VALID_GET_OPTIONS)
-		if err != nil {
-			return nil, err
-		}
-		p += str
+
+	str, err := options.toParameters(VALID_GET_OPTIONS)
+	if err != nil {
+		return nil, err
 	}
 	}
+	p += str
 
 
-	resp, err := c.sendRequest("GET", p, url.Values{})
+	resp, err := c.sendRequest("GET", p, nil)
 
 
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -75,28 +38,19 @@ func (c *Client) get(key string, options options) (*Response, error) {
 }
 }
 
 
 // put issues a PUT request
 // put issues a PUT request
-func (c *Client) put(key string, value string, ttl uint64, options options) (*Response, 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))
-	}
+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)
 	p := path.Join("keys", key)
 	p := path.Join("keys", key)
-	if options != nil {
-		str, err := optionsToString(options, VALID_PUT_OPTIONS)
-		if err != nil {
-			return nil, err
-		}
-		p += str
+
+	str, err := options.toParameters(VALID_PUT_OPTIONS)
+	if err != nil {
+		return nil, err
 	}
 	}
+	p += str
 
 
-	resp, err := c.sendRequest("PUT", p, v)
+	resp, err := c.sendRequest("PUT", p, buildValues(value, ttl))
 
 
 	if err != nil {
 	if err != nil {
 		return nil, err
 		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
 // 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)
 	logger.Debugf("post %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)
 
 
-	resp, err := c.sendRequest("POST", path.Join("keys", key), v)
+	resp, err := c.sendRequest("POST", p, buildValues(value, ttl))
 
 
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -128,20 +74,18 @@ func (c *Client) post(key string, value string, ttl uint64) (*Response, error) {
 }
 }
 
 
 // delete issues a DELETE request
 // 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)
 	logger.Debugf("delete %s [%s]", key, c.cluster.Leader)
-	v := url.Values{}
 
 
 	p := path.Join("keys", key)
 	p := path.Join("keys", key)
-	if options != nil {
-		str, err := optionsToString(options, VALID_DELETE_OPTIONS)
-		if err != nil {
-			return nil, err
-		}
-		p += str
+
+	str, err := options.toParameters(VALID_DELETE_OPTIONS)
+	if err != nil {
+		return nil, err
 	}
 	}
+	p += str
 
 
-	resp, err := c.sendRequest("DELETE", p, v)
+	resp, err := c.sendRequest("DELETE", p, nil)
 
 
 	if err != nil {
 	if err != nil {
 		return nil, err
 		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
 // 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
+func (c *Client) sendRequest(method string, relativePath string,
+	values url.Values) (*RawResponse, error) {
+
 	var req *http.Request
 	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
 	// if we connect to a follower, we will retry until we found a leader
 	for {
 	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 {
 		} 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
 		// Return a cURL command if curlChan is set
-		if curlChan != nil {
+		if c.cURLch != nil {
 			command := fmt.Sprintf("curl -X %s %s", method, httpPath)
 			command := fmt.Sprintf("curl -X %s %s", method, httpPath)
 			for key, value := range values {
 			for key, value := range values {
 				command += fmt.Sprintf(" -d %s=%s", key, value[0])
 				command += fmt.Sprintf(" -d %s=%s", key, value[0])
 			}
 			}
-			curlChan <- command
+			c.sendCURL(command)
 		}
 		}
 
 
 		logger.Debug("send.request.to ", httpPath, " | method ", method)
 		logger.Debug("send.request.to ", httpPath, " | method ", method)
-		if body == "" {
 
 
+		if values == nil {
 			req, _ = http.NewRequest(method, httpPath, nil)
 			req, _ = http.NewRequest(method, httpPath, nil)
-
 		} else {
 		} 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()))
 
 
-		resp, err = c.httpClient.Do(req)
+			req.Header.Set("Content-Type",
+				"application/x-www-form-urlencoded; param=value")
+		}
 
 
-		logger.Debug("recv.response.from ", httpPath)
 		// network error, change a machine!
 		// 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)
 			time.Sleep(time.Millisecond * 200)
 			continue
 			continue
 		}
 		}
 
 
 		if resp != nil {
 		if resp != nil {
-			if resp.StatusCode == http.StatusTemporaryRedirect {
-				httpPath := resp.Header.Get("Location")
-
-				resp.Body.Close()
-
-				if httpPath == "" {
-					return nil, errors.New("Cannot get redirection location")
-				}
+			logger.Debug("recv.response.from ", httpPath)
 
 
-				c.updateLeader(httpPath)
-				logger.Debug("send.redirect")
-				// try to connect the leader
-				continue
-			} else if resp.StatusCode == http.StatusInternalServerError {
-				resp.Body.Close()
+			var ok bool
+			ok, b = c.handleResp(resp)
 
 
-				retry++
-				if retry > 2*len(c.cluster.Machines) {
-					return nil, errors.New("Cannot reach servers")
-				}
+			if !ok {
 				continue
 				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
 		return nil, err
 	}
 	}
 
 
-	// Convert HTTP response to etcd response
-	b, err := ioutil.ReadAll(resp.Body)
+	r := &RawResponse{
+		StatusCode: resp.StatusCode,
+		Body:       b,
+		Header:     resp.Header,
+	}
 
 
-	resp.Body.Close()
+	return r, nil
+}
 
 
-	if err != nil {
-		return nil, err
-	}
+// 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()
 
 
-	if !(resp.StatusCode == http.StatusOK ||
-		resp.StatusCode == http.StatusCreated) {
-		return nil, handleError(b)
-	}
+	code := resp.StatusCode
 
 
-	var result Response
+	if code == http.StatusTemporaryRedirect {
+		u, err := resp.Location()
 
 
-	err = json.Unmarshal(b, &result)
+		if err != nil {
+			logger.Warning(err)
+		} else {
+			c.updateLeader(u)
+		}
 
 
-	if err != nil {
-		return nil, err
+		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
 	}
 	}
 
 
-	return &result, nil
+	logger.Warning("bad status code ", resp.StatusCode)
+	return false, nil
 }
 }
 
 
 func (c *Client) getHttpPath(random bool, s ...string) string {
 func (c *Client) getHttpPath(random bool, s ...string) string {
@@ -288,3 +234,18 @@ func (c *Client) getHttpPath(random bool, s ...string) string {
 
 
 	return fullPath
 	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
+}

+ 47 - 30
third_party/github.com/coreos/go-etcd/etcd/response.go

@@ -1,51 +1,68 @@
 package etcd
 package etcd
 
 
 import (
 import (
+	"encoding/json"
+	"net/http"
 	"time"
 	"time"
 )
 )
 
 
-// The response object from the server.
-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"`
+const (
+	rawResponse = iota
+	normalResponse
+)
+
+type responseType int
+
+type RawResponse struct {
+	StatusCode int
+	Body       []byte
+	Header     http.Header
+}
 
 
-	// If the key did not exist before the action,
-	// this field should be set to true
-	NewKey bool `json:"newKey,omitempty"`
+func (rr *RawResponse) toResponse() (*Response, error) {
+	if rr.StatusCode == http.StatusBadRequest {
+		return nil, handleError(rr.Body)
+	}
 
 
-	Expiration *time.Time `json:"expiration,omitempty"`
+	resp := new(Response)
 
 
-	// Time to live in second
-	TTL int64 `json:"ttl,omitempty"`
+	err := json.Unmarshal(rr.Body, resp)
 
 
-	// The command index of the raft machine when the command is executed
-	ModifiedIndex uint64 `json:"modifiedIndex"`
+	if err != nil {
+		return nil, err
+	}
+
+	return resp, nil
+}
+
+type Response struct {
+	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
 // 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 - 8
third_party/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go

@@ -7,13 +7,12 @@ import (
 
 
 func TestSetCurlChan(t *testing.T) {
 func TestSetCurlChan(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
+	c.OpenCURL()
+
 	defer func() {
 	defer func() {
-		c.DeleteAll("foo")
+		c.Delete("foo", true)
 	}()
 	}()
 
 
-	curlChan := make(chan string, 1)
-	SetCurlChan(curlChan)
-
 	_, err := c.Set("foo", "bar", 5)
 	_, err := c.Set("foo", "bar", 5)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
@@ -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",
 	expected := fmt.Sprintf("curl -X PUT %s/v2/keys/foo -d value=bar -d ttl=5",
 		c.cluster.Leader)
 		c.cluster.Leader)
-	actual := <-curlChan
+	actual := c.RecvCURL()
 	if expected != actual {
 	if expected != actual {
 		t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
 		t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
 			actual, expected)
 			actual, expected)
 	}
 	}
 
 
 	c.SetConsistency(STRONG_CONSISTENCY)
 	c.SetConsistency(STRONG_CONSISTENCY)
-	_, err = c.Get("foo", false)
+	_, err = c.Get("foo", false, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		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)
 		c.cluster.Leader)
-	actual = <-curlChan
+	actual = c.RecvCURL()
 	if expected != actual {
 	if expected != actual {
 		t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
 		t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
 			actual, expected)
 			actual, expected)

+ 81 - 13
third_party/github.com/coreos/go-etcd/etcd/set_update_create.go

@@ -2,42 +2,110 @@ package etcd
 
 
 // SetDir sets the given key to a directory.
 // SetDir sets the given key to a directory.
 func (c *Client) SetDir(key string, ttl uint64) (*Response, error) {
 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
 // UpdateDir updates the given key to a directory.  It succeeds only if the
 // given key already exists.
 // given key already exists.
 func (c *Client) UpdateDir(key string, ttl uint64) (*Response, error) {
 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
 // UpdateDir creates a directory under the given key.  It succeeds only if
 // the given key does not yet exist.
 // the given key does not yet exist.
 func (c *Client) CreateDir(key string, ttl uint64) (*Response, error) {
 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.
 // Set sets the given key to the given value.
 func (c *Client) Set(key string, value string, ttl uint64) (*Response, error) {
 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
 // Update updates the given key to the given value.  It succeeds only if the
 // given key already exists.
 // given key already exists.
 func (c *Client) Update(key string, value string, ttl uint64) (*Response, error) {
 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
 // Create creates a file with the given value under the given key.  It succeeds
 // only if the given key does not yet exist.
 // only if the given key does not yet exist.
 func (c *Client) Create(key string, value string, ttl uint64) (*Response, error) {
 func (c *Client) Create(key string, value string, ttl uint64) (*Response, error) {
-	return c.put(key, value, ttl, options{
+	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,
 		"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)
 }
 }

+ 27 - 28
third_party/github.com/coreos/go-etcd/etcd/set_update_create_test.go

@@ -7,14 +7,14 @@ import (
 func TestSet(t *testing.T) {
 func TestSet(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("foo")
+		c.Delete("foo", true)
 	}()
 	}()
 
 
 	resp, err := c.Set("foo", "bar", 5)
 	resp, err := c.Set("foo", "bar", 5)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		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)
 		t.Fatalf("Set 1 failed: %#v", resp)
 	}
 	}
 
 
@@ -22,8 +22,8 @@ func TestSet(t *testing.T) {
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		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)
 		t.Fatalf("Set 2 failed: %#v", resp)
 	}
 	}
 }
 }
@@ -31,12 +31,12 @@ func TestSet(t *testing.T) {
 func TestUpdate(t *testing.T) {
 func TestUpdate(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("foo")
-		c.DeleteAll("nonexistent")
+		c.Delete("foo", true)
+		c.Delete("nonexistent", true)
 	}()
 	}()
 
 
 	resp, err := c.Set("foo", "bar", 5)
 	resp, err := c.Set("foo", "bar", 5)
-	t.Logf("%#v", resp)
+
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -47,8 +47,8 @@ func TestUpdate(t *testing.T) {
 		t.Fatal(err)
 		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)
 		t.Fatalf("Update 1 failed: %#v", resp)
 	}
 	}
 
 
@@ -56,14 +56,14 @@ func TestUpdate(t *testing.T) {
 	resp, err = c.Update("nonexistent", "whatever", 5)
 	resp, err = c.Update("nonexistent", "whatever", 5)
 	if err == nil {
 	if err == nil {
 		t.Fatalf("The key %v did not exist, so the update should have failed."+
 		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) {
 func TestCreate(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("newKey")
+		c.Delete("newKey", true)
 	}()
 	}()
 
 
 	newKey := "/newKey"
 	newKey := "/newKey"
@@ -75,8 +75,8 @@ func TestCreate(t *testing.T) {
 		t.Fatal(err)
 		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)
 		t.Fatalf("Create 1 failed: %#v", resp)
 	}
 	}
 
 
@@ -84,22 +84,22 @@ func TestCreate(t *testing.T) {
 	resp, err = c.Create(newKey, newValue, 5)
 	resp, err = c.Create(newKey, newValue, 5)
 	if err == nil {
 	if err == nil {
 		t.Fatalf("The key %v did exist, so the creation should have failed."+
 		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) {
 func TestSetDir(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("foo")
-		c.DeleteAll("fooDir")
+		c.Delete("foo", true)
+		c.Delete("fooDir", true)
 	}()
 	}()
 
 
 	resp, err := c.SetDir("fooDir", 5)
 	resp, err := c.SetDir("fooDir", 5)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		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)
 		t.Fatalf("SetDir 1 failed: %#v", resp)
 	}
 	}
 
 
@@ -120,8 +120,8 @@ func TestSetDir(t *testing.T) {
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		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)
 		t.Fatalf("SetDir 2 failed: %#v", resp)
 	}
 	}
 }
 }
@@ -129,11 +129,10 @@ func TestSetDir(t *testing.T) {
 func TestUpdateDir(t *testing.T) {
 func TestUpdateDir(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("fooDir")
+		c.Delete("fooDir", true)
 	}()
 	}()
 
 
 	resp, err := c.SetDir("fooDir", 5)
 	resp, err := c.SetDir("fooDir", 5)
-	t.Logf("%#v", resp)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -144,8 +143,8 @@ func TestUpdateDir(t *testing.T) {
 		t.Fatal(err)
 		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)
 		t.Fatalf("UpdateDir 1 failed: %#v", resp)
 	}
 	}
 
 
@@ -153,14 +152,14 @@ func TestUpdateDir(t *testing.T) {
 	resp, err = c.UpdateDir("nonexistentDir", 5)
 	resp, err = c.UpdateDir("nonexistentDir", 5)
 	if err == nil {
 	if err == nil {
 		t.Fatalf("The key %v did not exist, so the update should have failed."+
 		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) {
 func TestCreateDir(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("fooDir")
+		c.Delete("fooDir", true)
 	}()
 	}()
 
 
 	// This should succeed
 	// This should succeed
@@ -169,8 +168,8 @@ func TestCreateDir(t *testing.T) {
 		t.Fatal(err)
 		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)
 		t.Fatalf("CreateDir 1 failed: %#v", resp)
 	}
 	}
 
 
@@ -178,6 +177,6 @@ func TestCreateDir(t *testing.T) {
 	resp, err = c.CreateDir("fooDir", 5)
 	resp, err = c.CreateDir("fooDir", 5)
 	if err == nil {
 	if err == nil {
 		t.Fatalf("The key %v did exist, so the creation should have failed."+
 		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)
 	}
 	}
 }
 }

+ 62 - 31
third_party/github.com/coreos/go-etcd/etcd/watch.go

@@ -9,44 +9,74 @@ var (
 	ErrWatchStoppedByUser = errors.New("Watch stopped by the user via stop channel")
 	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.
 //
 //
-// 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.
+// 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
 // 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)
+//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 {
+		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) watch(prefix string, waitIndex uint64, recursive bool, receiver chan *Response, stop chan bool) (*Response, error) {
-	logger.Debugf("watch %s [%s]", prefix, c.cluster.Leader)
+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 {
 	if receiver == nil {
 		return c.watchOnce(prefix, waitIndex, recursive, stop)
 		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
-			}
+	}
+
+	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
 	return nil, nil
@@ -54,9 +84,9 @@ func (c *Client) watch(prefix string, waitIndex uint64, recursive bool, receiver
 
 
 // helper func
 // helper func
 // return when there is change under the given prefix
 // 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)
 	errChan := make(chan error)
 
 
 	go func() {
 	go func() {
@@ -74,6 +104,7 @@ func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop ch
 
 
 		if err != nil {
 		if err != nil {
 			errChan <- err
 			errChan <- err
+			return
 		}
 		}
 
 
 		respChan <- resp
 		respChan <- resp

+ 12 - 12
third_party/github.com/coreos/go-etcd/etcd/watch_test.go

@@ -9,26 +9,26 @@ import (
 func TestWatch(t *testing.T) {
 func TestWatch(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("watch_foo")
+		c.Delete("watch_foo", true)
 	}()
 	}()
 
 
 	go setHelper("watch_foo", "bar", c)
 	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 {
 	if err != nil {
 		t.Fatal(err)
 		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)
 		t.Fatalf("Watch 1 failed: %#v", resp)
 	}
 	}
 
 
 	go setHelper("watch_foo", "bar", c)
 	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 {
 	if err != nil {
 		t.Fatal(err)
 		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)
 		t.Fatalf("Watch 2 failed: %#v", resp)
 	}
 	}
 
 
@@ -39,7 +39,7 @@ func TestWatch(t *testing.T) {
 
 
 	go receiver(ch, stop)
 	go receiver(ch, stop)
 
 
-	_, err = c.Watch("watch_foo", 0, ch, stop)
+	_, err = c.Watch("watch_foo", 0, false, ch, stop)
 	if err != ErrWatchStoppedByUser {
 	if err != ErrWatchStoppedByUser {
 		t.Fatalf("Watch returned a non-user stop error")
 		t.Fatalf("Watch returned a non-user stop error")
 	}
 	}
@@ -48,26 +48,26 @@ func TestWatch(t *testing.T) {
 func TestWatchAll(t *testing.T) {
 func TestWatchAll(t *testing.T) {
 	c := NewClient(nil)
 	c := NewClient(nil)
 	defer func() {
 	defer func() {
-		c.DeleteAll("watch_foo")
+		c.Delete("watch_foo", true)
 	}()
 	}()
 
 
 	go setHelper("watch_foo/foo", "bar", c)
 	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 {
 	if err != nil {
 		t.Fatal(err)
 		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)
 		t.Fatalf("WatchAll 1 failed: %#v", resp)
 	}
 	}
 
 
 	go setHelper("watch_foo/foo", "bar", c)
 	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 {
 	if err != nil {
 		t.Fatal(err)
 		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)
 		t.Fatalf("WatchAll 2 failed: %#v", resp)
 	}
 	}
 
 
@@ -78,7 +78,7 @@ func TestWatchAll(t *testing.T) {
 
 
 	go receiver(ch, stop)
 	go receiver(ch, stop)
 
 
-	_, err = c.WatchAll("watch_foo", 0, ch, stop)
+	_, err = c.Watch("watch_foo", 0, true, ch, stop)
 	if err != ErrWatchStoppedByUser {
 	if err != ErrWatchStoppedByUser {
 		t.Fatalf("Watch returned a non-user stop error")
 		t.Fatalf("Watch returned a non-user stop error")
 	}
 	}

+ 1 - 0
third_party/github.com/gorilla/context/README.md

@@ -1,5 +1,6 @@
 context
 context
 =======
 =======
+[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
 
 
 gorilla/context is a general purpose registry for global request variables.
 gorilla/context is a general purpose registry for global request variables.
 
 

+ 1 - 1
third_party/github.com/gorilla/context/context.go

@@ -92,7 +92,7 @@ func Purge(maxAge int) int {
 		datat = make(map[*http.Request]int64)
 		datat = make(map[*http.Request]int64)
 	} else {
 	} else {
 		min := time.Now().Unix() - int64(maxAge)
 		min := time.Now().Unix() - int64(maxAge)
-		for r, _ := range data {
+		for r := range data {
 			if datat[r] < min {
 			if datat[r] < min {
 				clear(r)
 				clear(r)
 				count++
 				count++

+ 1 - 0
third_party/github.com/gorilla/mux/README.md

@@ -1,5 +1,6 @@
 mux
 mux
 ===
 ===
+[![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux)
 
 
 gorilla/mux is a powerful URL router and dispatcher.
 gorilla/mux is a powerful URL router and dispatcher.
 
 

+ 8 - 0
third_party/github.com/gorilla/mux/mux.go

@@ -67,6 +67,14 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
 func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	// Clean path to canonical form and redirect.
 	// Clean path to canonical form and redirect.
 	if p := cleanPath(req.URL.Path); p != req.URL.Path {
 	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.Header().Set("Location", p)
 		w.WriteHeader(http.StatusMovedPermanently)
 		w.WriteHeader(http.StatusMovedPermanently)
 		return
 		return

+ 52 - 1
third_party/github.com/gorilla/mux/mux_test.go

@@ -22,6 +22,7 @@ type routeTest struct {
 	shouldMatch bool              // whether the request is expected to match the route at all
 	shouldMatch bool              // whether the request is expected to match the route at all
 }
 }
 
 
+
 func TestHost(t *testing.T) {
 func TestHost(t *testing.T) {
 	// newRequestHost a new request with a method, url, and host header
 	// newRequestHost a new request with a method, url, and host header
 	newRequestHost := func(method, url, host string) *http.Request {
 	newRequestHost := func(method, url, host string) *http.Request {
@@ -416,6 +417,15 @@ func TestQueries(t *testing.T) {
 			path:        "",
 			path:        "",
 			shouldMatch: true,
 			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",
 			title:       "Queries route, bad query",
 			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
 			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
@@ -663,7 +673,7 @@ func testRoute(t *testing.T, test routeTest) {
 func TestKeepContext(t *testing.T) {
 func TestKeepContext(t *testing.T) {
 	func1 := func(w http.ResponseWriter, r *http.Request) {}
 	func1 := func(w http.ResponseWriter, r *http.Request) {}
 
 
-	r := NewRouter()
+	r:= NewRouter()
 	r.HandleFunc("/", func1).Name("func1")
 	r.HandleFunc("/", func1).Name("func1")
 
 
 	req, _ := http.NewRequest("GET", "http://localhost/", nil)
 	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
 // https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
 func TestSubrouterHeader(t *testing.T) {
 func TestSubrouterHeader(t *testing.T) {
 	expected := "func1 response"
 	expected := "func1 response"

+ 4 - 4
third_party/github.com/gorilla/mux/old_test.go

@@ -96,8 +96,8 @@ func TestRouteMatchers(t *testing.T) {
 		method = "GET"
 		method = "GET"
 		headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
 		headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
 		resultVars = map[bool]map[string]string{
 		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"
 		method = "POST"
 		headers = map[string]string{"Content-Type": "application/json"}
 		headers = map[string]string{"Content-Type": "application/json"}
 		resultVars = map[bool]map[string]string{
 		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: {},
 		}
 		}
 	}
 	}