Browse Source

Merge branch 'master' of https://github.com/coreos/etcd into mod-lock

Conflicts:
	server/v2/tests/delete_handler_test.go
	server/v2/tests/get_handler_test.go
	server/v2/tests/post_handler_test.go
	server/v2/tests/put_handler_test.go
	third_party/github.com/coreos/go-etcd/etcd/requests.go
Ben Johnson 12 years ago
parent
commit
5d865e321c
61 changed files with 1420 additions and 784 deletions
  1. 2 2
      Documentation/internal-protocol-versioning.md
  2. 300 60
      README.md
  3. 2 0
      error/error.go
  4. 6 6
      mod/lock/v2/acquire_handler.go
  5. 1 1
      mod/lock/v2/get_index_handler.go
  6. 2 2
      mod/lock/v2/handler.go
  7. 1 1
      mod/lock/v2/release_handler.go
  8. 3 3
      server/registry.go
  9. 5 0
      server/server.go
  10. 3 2
      server/usage.go
  11. 1 1
      server/v2/get_handler.go
  12. 1 1
      server/v2/tests/delete_handler_test.go
  13. 32 26
      server/v2/tests/get_handler_test.go
  14. 11 5
      server/v2/tests/post_handler_test.go
  15. 17 11
      server/v2/tests/put_handler_test.go
  16. 1 0
      server/v2/v2.go
  17. 26 32
      store/event.go
  18. 17 4
      store/event_history.go
  19. 10 10
      store/event_test.go
  20. 1 1
      store/heap_test.go
  21. 0 31
      store/kv_pairs.go
  22. 46 44
      store/node.go
  23. 36 0
      store/node_extern.go
  24. 64 45
      store/store.go
  25. 82 62
      store/store_test.go
  26. 9 9
      store/ttl_key_heap.go
  27. 8 8
      store/watcher_hub.go
  28. 23 3
      store/watcher_test.go
  29. 2 2
      tests/functional/multi_node_kill_all_and_recovery_test.go
  30. 10 10
      tests/functional/remove_node_test.go
  31. 6 4
      tests/functional/simple_multi_node_test.go
  32. 6 4
      tests/functional/simple_snapshot_test.go
  33. 8 5
      tests/functional/single_node_recovery_test.go
  34. 9 6
      tests/functional/single_node_test.go
  35. 1 1
      tests/functional/util.go
  36. 1 1
      tests/functional/v1_migration_test.go
  37. 5 0
      tests/mock/server_v2.go
  38. 14 2
      third_party/github.com/coreos/go-etcd/etcd/add_child.go
  39. 8 8
      third_party/github.com/coreos/go-etcd/etcd/add_child_test.go
  40. 35 10
      third_party/github.com/coreos/go-etcd/etcd/client.go
  41. 8 1
      third_party/github.com/coreos/go-etcd/etcd/compare_and_swap.go
  42. 6 6
      third_party/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go
  43. 2 3
      third_party/github.com/coreos/go-etcd/etcd/debug.go
  44. 20 11
      third_party/github.com/coreos/go-etcd/etcd/delete.go
  45. 13 13
      third_party/github.com/coreos/go-etcd/etcd/delete_test.go
  46. 21 17
      third_party/github.com/coreos/go-etcd/etcd/get.go
  47. 46 25
      third_party/github.com/coreos/go-etcd/etcd/get_test.go
  48. 68 0
      third_party/github.com/coreos/go-etcd/etcd/options.go
  49. 118 157
      third_party/github.com/coreos/go-etcd/etcd/requests.go
  50. 47 30
      third_party/github.com/coreos/go-etcd/etcd/response.go
  51. 7 8
      third_party/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go
  52. 81 13
      third_party/github.com/coreos/go-etcd/etcd/set_update_create.go
  53. 27 28
      third_party/github.com/coreos/go-etcd/etcd/set_update_create_test.go
  54. 62 31
      third_party/github.com/coreos/go-etcd/etcd/watch.go
  55. 12 12
      third_party/github.com/coreos/go-etcd/etcd/watch_test.go
  56. 1 0
      third_party/github.com/gorilla/context/README.md
  57. 1 1
      third_party/github.com/gorilla/context/context.go
  58. 1 0
      third_party/github.com/gorilla/mux/README.md
  59. 8 0
      third_party/github.com/gorilla/mux/mux.go
  60. 52 1
      third_party/github.com/gorilla/mux/mux_test.go
  61. 4 4
      third_party/github.com/gorilla/mux/old_test.go

+ 2 - 2
Documentation/internal-protocol-versioning.md

@@ -4,9 +4,9 @@ Goal: We want to be able to upgrade an individual peer in an etcd cluster to a n
 The process will take the form of individual followers upgrading to the latest version until the entire cluster is on the new version.
 The process will take the form of individual followers upgrading to the latest version until the entire cluster is on the new version.
 
 
 Immediate need: etcd is moving too fast to version the internal API right now.
 Immediate need: etcd is moving too fast to version the internal API right now.
-But, we need to keep mixed version clusters from being started by a rollowing upgrade process (e.g. the CoreOS developer alpha).
+But, we need to keep mixed version clusters from being started by a rolling upgrade process (e.g. the CoreOS developer alpha).
 
 
-Longer term need: Having a mixed version cluster where all peers are not be running the exact same version of etcd itself but are able to speak one version of the internal protocol.
+Longer term need: Having a mixed version cluster where all peers are not running the exact same version of etcd itself but are able to speak one version of the internal protocol.
 
 
 Solution: The internal protocol needs to be versioned just as the client protocol is.
 Solution: The internal protocol needs to be versioned just as the client protocol is.
 Initially during the 0.\*.\* series of etcd releases we won't allow mixed versions at all.
 Initially during the 0.\*.\* series of etcd releases we won't allow mixed versions at all.

+ 300 - 60
README.md

@@ -56,18 +56,18 @@ go version
 ```
 ```
 
 
 
 
-### Running a single node
+### Running a single machine
 
 
-These examples will use a single node cluster to show you the basics of the etcd REST API.
+These examples will use a single machine cluster to show you the basics of the etcd REST API.
 Let's start etcd:
 Let's start etcd:
 
 
 ```sh
 ```sh
-./etcd -data-dir node0 -name node0
+./etcd -data-dir machine0 -name machine0
 ```
 ```
 
 
-This will bring up an etcd node listening on port 4001 for client communication and on port 7001 for server-to-server communication.
-The `-data-dir node0` argument tells etcd to write node configuration, logs and snapshots to the `./node0/` directory.
-The `-name node0` tells the rest of the cluster that this node is named node0.
+This will bring up etcd listening on port 4001 for client communication and on port 7001 for server-to-server communication.
+The `-data-dir machine0` argument tells etcd to write machine configuration, logs and snapshots to the `./machine0/` directory.
+The `-name machine` tells the rest of the cluster that this machine is named machine0.
 
 
 
 
 
 
@@ -75,7 +75,7 @@ The `-name node0` tells the rest of the cluster that this node is named node0.
 
 
 ### Setting the value to a key
 ### Setting the value to a key
 
 
-Let’s set the first key-value pair to the node.
+Let’s set the first key-value pair to the datastore.
 In this case the key is `/message` and the value is `Hello world`.
 In this case the key is `/message` and the value is `Hello world`.
 
 
 ```sh
 ```sh
@@ -83,7 +83,15 @@ curl -L http://127.0.0.1:4001/v2/keys/message -X PUT -d value="Hello world"
 ```
 ```
 
 
 ```json
 ```json
-{"action":"set","key":"/message","value":"Hello world","modifiedIndex":2}
+{
+    "action": "set",
+    "node": {
+        "createdIndex": 2,
+        "key": "/message",
+        "modifiedIndex": 2,
+        "value": "Hello world"
+    }
+}
 ```
 ```
 
 
 This response contains four fields.
 This response contains four fields.
@@ -112,7 +120,15 @@ curl -L http://127.0.0.1:4001/v2/keys/message
 ```
 ```
 
 
 ```json
 ```json
-{"action":"get","key":"/message","value":"Hello world","modifiedIndex":2}
+{
+    "action": "get",
+    "node": {
+        "createdIndex": 2,
+        "key": "/message",
+        "modifiedIndex": 2,
+        "value": "Hello world"
+    }
+}
 ```
 ```
 
 
 
 
@@ -121,14 +137,23 @@ curl -L http://127.0.0.1:4001/v2/keys/message
 You can change the value of `/message` from `Hello world` to `Hello etcd` with another `PUT` request to the key:
 You can change the value of `/message` from `Hello world` to `Hello etcd` with another `PUT` request to the key:
 
 
 ```sh
 ```sh
-curl -L http://127.0.0.1:4001/v1/keys/message -XPUT -d value="Hello etcd"
+curl -L http://127.0.0.1:4001/v2/keys/message -XPUT -d value="Hello etcd"
 ```
 ```
 
 
 ```json
 ```json
-{"action":"set","key":"/message","prevValue":"Hello world","value":"Hello etcd","index":3}
-```
-
-Notice that the `prevValue` is set to the previous value of the key - `Hello world`.
+{
+    "action": "set",
+    "node": {
+        "createdIndex": 3,
+        "key": "/message",
+        "modifiedIndex": 3,
+        "prevValue": "Hello world",
+        "value": "Hello etcd"
+    }
+}
+```
+
+Notice that `node.prevValue` is set to the previous value of the key - `Hello world`.
 It is useful when you want to atomically set a value to a key and get its old value.
 It is useful when you want to atomically set a value to a key and get its old value.
 
 
 
 
@@ -141,7 +166,15 @@ curl -L http://127.0.0.1:4001/v2/keys/message -XDELETE
 ```
 ```
 
 
 ```json
 ```json
-{"action":"delete","key":"/message","prevValue":"Hello etcd","modifiedIndex":4}
+{
+    "action": "delete",
+    "node": {
+        "createdIndex": 3,
+        "key": "/message",
+        "modifiedIndex": 4,
+        "prevValue": "Hello etcd"
+    }
+}
 ```
 ```
 
 
 
 
@@ -155,7 +188,17 @@ curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -d ttl=5
 ```
 ```
 
 
 ```json
 ```json
-{"action":"set","key":"/foo","value":"bar","expiration":"2013-11-12T20:21:22.629352334-05:00","ttl":5,"modifiedIndex":5}
+{
+    "action": "set",
+    "node": {
+        "createdIndex": 5,
+        "expiration": "2013-12-04T12:01:21.874888581-08:00",
+        "key": "/foo",
+        "modifiedIndex": 5,
+        "ttl": 5,
+        "value": "bar"
+    }
+}
 ```
 ```
 
 
 Note the two new fields in response:
 Note the two new fields in response:
@@ -164,7 +207,7 @@ Note the two new fields in response:
 
 
 2. The `ttl` is the time to live for the key, in seconds.
 2. The `ttl` is the time to live for the key, in seconds.
 
 
-_NOTE_: Keys can only be expired by a cluster leader so if a node gets disconnected from the cluster, its keys will not expire until it rejoins.
+_NOTE_: Keys can only be expired by a cluster leader so if a machine gets disconnected from the cluster, its keys will not expire until it rejoins.
 
 
 Now you can try to get the key by sending a `GET` request:
 Now you can try to get the key by sending a `GET` request:
 
 
@@ -175,7 +218,12 @@ curl -L http://127.0.0.1:4001/v2/keys/foo
 If the TTL has expired, the key will be deleted, and you will be returned a 100.
 If the TTL has expired, the key will be deleted, and you will be returned a 100.
 
 
 ```json
 ```json
-{"errorCode":100,"message":"Key Not Found","cause":"/foo","index":6}
+{
+    "cause": "/foo",
+    "errorCode": 100,
+    "index": 6,
+    "message": "Key Not Found"
+}
 ```
 ```
 
 
 
 
@@ -201,7 +249,15 @@ curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar
 The first terminal should get the notification and return with the same response as the set request.
 The first terminal should get the notification and return with the same response as the set request.
 
 
 ```json
 ```json
-{"action":"set","key":"/foo","value":"bar","modifiedIndex":7}
+{
+    "action": "set",
+    "node": {
+        "createdIndex": 7,
+        "key": "/foo",
+        "modifiedIndex": 7,
+        "value": "bar"
+    }
+}
 ```
 ```
 
 
 However, the watch command can do more than this.
 However, the watch command can do more than this.
@@ -219,9 +275,9 @@ The watch command returns immediately with the same response as previous.
 
 
 ### Atomic Compare-and-Swap (CAS)
 ### Atomic Compare-and-Swap (CAS)
 
 
-Etcd can be used as a centralized coordination service in a cluster and `CompareAndSwap` is the most basic operation to build distributed lock service. 
+Etcd can be used as a centralized coordination service in a cluster and `CompareAndSwap` is the most basic operation to build distributed lock service.
 
 
-This command will set the value of a key only if the client-provided conditions are equal to the current conditions. 
+This command will set the value of a key only if the client-provided conditions are equal to the current conditions.
 
 
 The current comparable conditions are:
 The current comparable conditions are:
 
 
@@ -235,20 +291,42 @@ Here is a simple example.
 Let's create a key-value pair first: `foo=one`.
 Let's create a key-value pair first: `foo=one`.
 
 
 ```sh
 ```sh
-curl -L http://127.0.0.1:4001/v1/keys/foo -XPUT -d value=one
+curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=one
 ```
 ```
 
 
-Let's try an invalid `CompareAndSwap` command first.
-We can provide the `prevValue` parameter to the set command to make it a `CompareAndSwap` command.
+Let's try some invalid `CompareAndSwap` commands first.
 
 
+Trying to set this existing key with `prevExist=false` fails as expected:
 ```sh
 ```sh
-curl -L http://127.0.0.1:4001/v1/keys/foo?prevValue=two -XPUT -d value=three
+curl -L http://127.0.0.1:4001/v2/keys/foo?prevExist=false -XPUT -d value=three
+```
+
+The error code explains the problem:
+
+```json
+{
+    "cause": "/foo",
+    "errorCode": 105,
+    "index": 39776,
+    "message": "Already exists"
+}
+```
+
+Now lets provide a `prevValue` parameter:
+
+```sh
+curl -L http://127.0.0.1:4001/v2/keys/foo?prevValue=two -XPUT -d value=three
 ```
 ```
 
 
 This will try to compare the previous value of the key and the previous value we provided. If they are equal, the value of the key will change to three.
 This will try to compare the previous value of the key and the previous value we provided. If they are equal, the value of the key will change to three.
 
 
 ```json
 ```json
-{"errorCode":101,"message":"Test Failed","cause":"[two != one] [0 != 8]","index":8}
+{
+    "cause": "[two != one] [0 != 8]",
+    "errorCode": 101,
+    "index": 8,
+    "message": "Test Failed"
+}
 ```
 ```
 
 
 which means `CompareAndSwap` failed.
 which means `CompareAndSwap` failed.
@@ -262,10 +340,19 @@ curl -L http://127.0.0.1:4001/v2/keys/foo?prevValue=one -XPUT -d value=two
 The response should be
 The response should be
 
 
 ```json
 ```json
-{"action":"compareAndSwap","key":"/foo","prevValue":"one","value":"two","modifiedIndex":9}
+{
+    "action": "compareAndSwap",
+    "node": {
+        "createdIndex": 8,
+        "key": "/foo",
+        "modifiedIndex": 9,
+        "prevValue": "one",
+        "value": "two"
+    }
+}
 ```
 ```
 
 
-We successfully changed the value from “one” to “two” since we gave the correct previous value.
+We successfully changed the value from "one" to "two" since we gave the correct previous value.
 
 
 
 
 ### Listing a directory
 ### Listing a directory
@@ -283,7 +370,15 @@ curl -L http://127.0.0.1:4001/v2/keys/foo_dir/foo -XPUT -d value=bar
 ```
 ```
 
 
 ```json
 ```json
-{"action":"set","key":"/foo_dir/foo","value":"bar","modifiedIndex":10}
+{
+    "action": "set",
+    "node": {
+        "createdIndex": 2,
+        "key": "/foo_dir/foo",
+        "modifiedIndex": 2,
+        "value": "bar"
+    }
+}
 ```
 ```
 
 
 Now we can list the keys under root `/`:
 Now we can list the keys under root `/`:
@@ -295,7 +390,21 @@ curl -L http://127.0.0.1:4001/v2/keys/
 We should see the response as an array of items:
 We should see the response as an array of items:
 
 
 ```json
 ```json
-{"action":"get","key":"/","dir":true,"kvs":[{"key":"/foo","value":"two","modifiedIndex":9},{"key":"/foo_dir","dir":true,"modifiedIndex":10}],"modifiedIndex":0}
+{
+    "action": "get",
+    "node": {
+        "dir": true,
+        "key": "/",
+        "nodes": [
+            {
+                "createdIndex": 2,
+                "dir": true,
+                "key": "/foo_dir",
+                "modifiedIndex": 2
+            }
+        ]
+    }
+}
 ```
 ```
 
 
 Here we can see `/foo` is a key-value pair under `/` and `/foo_dir` is a directory.
 Here we can see `/foo` is a key-value pair under `/` and `/foo_dir` is a directory.
@@ -306,7 +415,29 @@ curl -L http://127.0.0.1:4001/v2/keys/?recursive=true
 ```
 ```
 
 
 ```json
 ```json
-{"action":"get","key":"/","dir":true,"kvs":[{"key":"/foo","value":"two","modifiedIndex":9},{"key":"/foo_dir","dir":true,"kvs":[{"key":"/foo_dir/foo","value":"bar","modifiedIndex":10}],"modifiedIndex":10}],"modifiedIndex":0}
+{
+    "action": "get",
+    "node": {
+        "dir": true,
+        "key": "/",
+        "nodes": [
+            {
+                "createdIndex": 2,
+                "dir": true,
+                "key": "/foo_dir",
+                "modifiedIndex": 2,
+                "nodes": [
+                    {
+                        "createdIndex": 2,
+                        "key": "/foo_dir/foo",
+                        "modifiedIndex": 2,
+                        "value": "bar"
+                    }
+                ]
+            }
+        ]
+    }
+}
 ```
 ```
 
 
 
 
@@ -321,7 +452,15 @@ curl -L http://127.0.0.1:4001/v2/keys/foo_dir?recursive=true -XDELETE
 ```
 ```
 
 
 ```json
 ```json
-{"action":"delete","key":"/foo_dir","dir":true,"modifiedIndex":11}
+{
+    "action": "delete",
+    "node": {
+        "createdIndex": 10,
+        "dir": true,
+        "key": "/foo_dir",
+        "modifiedIndex": 11
+    }
+}
 ```
 ```
 
 
 
 
@@ -337,7 +476,15 @@ curl -L http://127.0.0.1:4001/v2/keys/_message -XPUT -d value="Hello hidden worl
 ```
 ```
 
 
 ```json
 ```json
-{"action":"set","key":"/_message","value":"Hello hidden world","modifiedIndex":12}
+{
+    "action": "set",
+    "node": {
+        "createdIndex": 3,
+        "key": "/_message",
+        "modifiedIndex": 3,
+        "value": "Hello hidden world"
+    }
+}
 ```
 ```
 
 
 
 
@@ -348,7 +495,15 @@ curl -L http://127.0.0.1:4001/v2/keys/message -XPUT -d value="Hello world"
 ```
 ```
 
 
 ```json
 ```json
-{"action":"set","key":"/message","value":"Hello world","modifiedIndex":13}
+{
+    "action": "set",
+    "node": {
+        "createdIndex": 4,
+        "key": "/message",
+        "modifiedIndex": 4,
+        "value": "Hello world"
+    }
+}
 ```
 ```
 
 
 Now let's try to get a listing of keys under the root directory, `/`:
 Now let's try to get a listing of keys under the root directory, `/`:
@@ -358,7 +513,27 @@ curl -L http://127.0.0.1:4001/v2/keys/
 ```
 ```
 
 
 ```json
 ```json
-{"action":"get","key":"/","dir":true,"kvs":[{"key":"/foo","value":"two","modifiedIndex":9},{"key":"/message","value":"Hello world","modifiedIndex":13}],"modifiedIndex":0}
+{
+    "action": "get",
+    "node": {
+        "dir": true,
+        "key": "/",
+        "nodes": [
+            {
+                "createdIndex": 2,
+                "dir": true,
+                "key": "/foo_dir",
+                "modifiedIndex": 2
+            },
+            {
+                "createdIndex": 4,
+                "key": "/message",
+                "modifiedIndex": 4,
+                "value": "Hello world"
+            }
+        ]
+    }
+}
 ```
 ```
 
 
 Here we see the `/message` key but our hidden `/_message` key is not returned.
 Here we see the `/message` key but our hidden `/_message` key is not returned.
@@ -378,12 +553,12 @@ For testing you can use the certificates in the `fixtures/ca` directory.
 Let's configure etcd to use this keypair:
 Let's configure etcd to use this keypair:
 
 
 ```sh
 ```sh
-./etcd -f -name node0 -data-dir node0 -cert-file=./fixtures/ca/server.crt -key-file=./fixtures/ca/server.key.insecure
+./etcd -f -name machine0 -data-dir machine0 -cert-file=./fixtures/ca/server.crt -key-file=./fixtures/ca/server.key.insecure
 ```
 ```
 
 
 There are a few new options we're using:
 There are a few new options we're using:
 
 
-* `-f` - forces a new node configuration, even if an existing configuration is found. (WARNING: data loss!)
+* `-f` - forces a new machine configuration, even if an existing configuration is found. (WARNING: data loss!)
 * `-cert-file` and `-key-file` specify the location of the cert and key files to be used for for transport layer security between the client and server.
 * `-cert-file` and `-key-file` specify the location of the cert and key files to be used for for transport layer security between the client and server.
 
 
 You can now test the configuration using HTTPS:
 You can now test the configuration using HTTPS:
@@ -394,6 +569,12 @@ curl --cacert ./fixtures/ca/server-chain.pem https://127.0.0.1:4001/v2/keys/foo
 
 
 You should be able to see the handshake succeed.
 You should be able to see the handshake succeed.
 
 
+**OSX 10.9+ Users**: curl 7.30.0 on OSX 10.9+ doesn't understand certificates passed in on the command line.
+Instead you must import the dummy ca.crt directly into the keychain or add the `-k` flag to curl to ignore errors.
+If you want to test without the `-k` flag run `open ./fixtures/ca/ca.crt` and follow the prompts.
+Please remove this certificate after you are done testing!
+If you know of a workaround let us know.
+
 ```
 ```
 ...
 ...
 SSLv3, TLS handshake, Finished (20):
 SSLv3, TLS handshake, Finished (20):
@@ -403,7 +584,13 @@ SSLv3, TLS handshake, Finished (20):
 And also the response from the etcd server:
 And also the response from the etcd server:
 
 
 ```json
 ```json
-{"action":"set","key":"/foo","prevValue":"bar","value":"bar","modifiedIndex":3}
+{
+    "action": "set",
+    "key": "/foo",
+    "modifiedIndex": 3,
+    "prevValue": "bar",
+    "value": "bar"
+}
 ```
 ```
 
 
 
 
@@ -413,7 +600,7 @@ We can also do authentication using CA certs.
 The clients will provide their cert to the server and the server will check whether the cert is signed by the CA and decide whether to serve the request.
 The clients will provide their cert to the server and the server will check whether the cert is signed by the CA and decide whether to serve the request.
 
 
 ```sh
 ```sh
-./etcd -f -name node0 -data-dir node0 -ca-file=./fixtures/ca/ca.crt -cert-file=./fixtures/ca/server.crt -key-file=./fixtures/ca/server.key.insecure
+./etcd -f -name machine0 -data-dir machine0 -ca-file=./fixtures/ca/ca.crt -cert-file=./fixtures/ca/server.crt -key-file=./fixtures/ca/server.key.insecure
 ```
 ```
 
 
 ```-ca-file``` is the path to the CA cert.
 ```-ca-file``` is the path to the CA cert.
@@ -435,7 +622,7 @@ routines:SSL3_READ_BYTES:sslv3 alert bad certificate
 We need to give the CA signed cert to the server.
 We need to give the CA signed cert to the server.
 
 
 ```sh
 ```sh
-curl --key ./fixtures/ca/server2.key.insecure --cert ./fixtures/ca/server2.crt --cacert ./fixtures/ca/server-chain.pem -L https://127.0.0.1:4001/v1/keys/foo -XPUT -d value=bar -v
+curl --key ./fixtures/ca/server2.key.insecure --cert ./fixtures/ca/server2.crt --cacert ./fixtures/ca/server-chain.pem -L https://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -v
 ```
 ```
 
 
 You should able to see:
 You should able to see:
@@ -450,7 +637,16 @@ TLS handshake, Finished (20)
 And also the response from the server:
 And also the response from the server:
 
 
 ```json
 ```json
-{"action":"set","key":"/foo","prevValue":"bar","value":"bar","modifiedIndex":3}
+{
+    "action": "set",
+    "node": {
+        "createdIndex": 12,
+        "key": "/foo",
+        "modifiedIndex": 12,
+        "prevValue": "two",
+        "value": "bar"
+    }
+}
 ```
 ```
 
 
 
 
@@ -463,29 +659,29 @@ We use Raft as the underlying distributed protocol which provides consistency an
 
 
 Let start by creating 3 new etcd instances.
 Let start by creating 3 new etcd instances.
 
 
-We use `-peer-addr` to specify server port and `-addr` to specify client port and `-data-dir` to specify the directory to store the log and info of the node in the cluster:
+We use `-peer-addr` to specify server port and `-addr` to specify client port and `-data-dir` to specify the directory to store the log and info of the machine in the cluster:
 
 
 ```sh
 ```sh
-./etcd -peer-addr 127.0.0.1:7001 -addr 127.0.0.1:4001 -data-dir nodes/node1 -name node1
+./etcd -peer-addr 127.0.0.1:7001 -addr 127.0.0.1:4001 -data-dir machines/machine1 -name machine1
 ```
 ```
 
 
 **Note:** If you want to run etcd on an external IP address and still have access locally, you'll need to add `-bind-addr 0.0.0.0` so that it will listen on both external and localhost addresses.
 **Note:** If you want to run etcd on an external IP address and still have access locally, you'll need to add `-bind-addr 0.0.0.0` so that it will listen on both external and localhost addresses.
 A similar argument `-peer-bind-addr` is used to setup the listening address for the server port.
 A similar argument `-peer-bind-addr` is used to setup the listening address for the server port.
 
 
-Let's join two more nodes to this cluster using the `-peers` argument:
+Let's join two more machines to this cluster using the `-peers` argument:
 
 
 ```sh
 ```sh
-./etcd -peer-addr 127.0.0.1:7002 -addr 127.0.0.1:4002 -peers 127.0.0.1:7001 -data-dir nodes/node2 -name node2
-./etcd -peer-addr 127.0.0.1:7003 -addr 127.0.0.1:4003 -peers 127.0.0.1:7001 -data-dir nodes/node3 -name node3
+./etcd -peer-addr 127.0.0.1:7002 -addr 127.0.0.1:4002 -peers 127.0.0.1:7001 -data-dir machines/machine2 -name machine2
+./etcd -peer-addr 127.0.0.1:7003 -addr 127.0.0.1:4003 -peers 127.0.0.1:7001 -data-dir machines/machine3 -name machine3
 ```
 ```
 
 
 We can retrieve a list of machines in the cluster using the HTTP API:
 We can retrieve a list of machines in the cluster using the HTTP API:
 
 
 ```sh
 ```sh
-curl -L http://127.0.0.1:4001/v1/machines
+curl -L http://127.0.0.1:4001/v2/machines
 ```
 ```
 
 
-We should see there are three nodes in the cluster
+We should see there are three machines in the cluster
 
 
 ```
 ```
 http://127.0.0.1:4001, http://127.0.0.1:4002, http://127.0.0.1:4003
 http://127.0.0.1:4001, http://127.0.0.1:4002, http://127.0.0.1:4003
@@ -494,11 +690,39 @@ http://127.0.0.1:4001, http://127.0.0.1:4002, http://127.0.0.1:4003
 The machine list is also available via the main key API:
 The machine list is also available via the main key API:
 
 
 ```sh
 ```sh
-curl -L http://127.0.0.1:4001/v1/keys/_etcd/machines
+curl -L http://127.0.0.1:4001/v2/keys/_etcd/machines
 ```
 ```
 
 
 ```json
 ```json
-[{"action":"get","key":"/_etcd/machines/node1","value":"raft=http://127.0.0.1:7001\u0026etcd=http://127.0.0.1:4001","index":1},{"action":"get","key":"/_etcd/machines/node2","value":"raft=http://127.0.0.1:7002\u0026etcd=http://127.0.0.1:4002","index":1},{"action":"get","key":"/_etcd/machines/node3","value":"raft=http://127.0.0.1:7003\u0026etcd=http://127.0.0.1:4003","index":1}]
+{
+    "action": "get",
+    "node": {
+        "createdIndex": 1,
+        "dir": true,
+        "key": "/_etcd/machines",
+        "modifiedIndex": 1,
+        "nodes": [
+            {
+                "createdIndex": 1,
+                "key": "/_etcd/machines/machine1",
+                "modifiedIndex": 1,
+                "value": "raft=http://127.0.0.1:7001&etcd=http://127.0.0.1:4001"
+            },
+            {
+                "createdIndex": 2,
+                "key": "/_etcd/machines/machine2",
+                "modifiedIndex": 2,
+                "value": "raft=http://127.0.0.1:7002&etcd=http://127.0.0.1:4002"
+            },
+            {
+                "createdIndex": 3,
+                "key": "/_etcd/machines/machine3",
+                "modifiedIndex": 3,
+                "value": "raft=http://127.0.0.1:7003&etcd=http://127.0.0.1:4003"
+            }
+        ]
+    }
+}
 ```
 ```
 
 
 We can also get the current leader in the cluster:
 We can also get the current leader in the cluster:
@@ -520,7 +744,15 @@ curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar
 ```
 ```
 
 
 ```json
 ```json
-{"action":"set","key":"/foo","value":"bar","modifiedIndex":4}
+{
+    "action": "set",
+    "node": {
+        "createdIndex": 4,
+        "key": "/foo",
+        "modifiedIndex": 4,
+        "value": "bar"
+    }
+}
 ```
 ```
 
 
 
 
@@ -529,13 +761,13 @@ curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar
 Now if we kill the leader of the cluster, we can get the value from one of the other two machines:
 Now if we kill the leader of the cluster, we can get the value from one of the other two machines:
 
 
 ```sh
 ```sh
-curl -L http://127.0.0.1:4002/v1/keys/foo
+curl -L http://127.0.0.1:4002/v2/keys/foo
 ```
 ```
 
 
 We can also see that a new leader has been elected:
 We can also see that a new leader has been elected:
 
 
 ```
 ```
-curl -L http://127.0.0.1:4002/v1/leader
+curl -L http://127.0.0.1:4002/v2/leader
 ```
 ```
 
 
 ```
 ```
@@ -551,17 +783,25 @@ http://127.0.0.1:7003
 
 
 ### Testing Persistence
 ### Testing Persistence
 
 
-Next we'll kill all the nodes to test persistence.
-Type `CTRL-C` on each terminal and then rerun the same command you used to start each node.
+Next we'll kill all the machines to test persistence.
+Type `CTRL-C` on each terminal and then rerun the same command you used to start each machine.
 
 
 Your request for the `foo` key will return the correct value:
 Your request for the `foo` key will return the correct value:
 
 
 ```sh
 ```sh
-curl -L http://127.0.0.1:4002/v1/keys/foo
+curl -L http://127.0.0.1:4002/v2/keys/foo
 ```
 ```
 
 
 ```json
 ```json
-{"action":"get","key":"/foo","value":"bar","index":4}
+{
+    "action": "get",
+    "node": {
+        "createdIndex": 4,
+        "key": "/foo",
+        "modifiedIndex": 4,
+        "value": "bar"
+    }
+}
 ```
 ```
 
 
 
 
@@ -654,8 +894,8 @@ The command is not committed until the majority of the cluster peers receive tha
 Because of this majority voting property, the ideal cluster should be kept small to keep speed up and be made up of an odd number of peers.
 Because of this majority voting property, the ideal cluster should be kept small to keep speed up and be made up of an odd number of peers.
 
 
 Odd numbers are good because if you have 8 peers the majority will be 5 and if you have 9 peers the majority will still be 5.
 Odd numbers are good because if you have 8 peers the majority will be 5 and if you have 9 peers the majority will still be 5.
-The result is that an 8 peer cluster can tolerate 3 peer failures and a 9 peer cluster can tolerate 4 nodes failures.
-And in the best case when all 9 peers are responding the cluster will perform at the speed of the fastest 5 nodes.
+The result is that an 8 peer cluster can tolerate 3 peer failures and a 9 peer cluster can tolerate 4 machine failures.
+And in the best case when all 9 peers are responding the cluster will perform at the speed of the fastest 5 machines.
 
 
 
 
 ### Why SSLv3 alert handshake failure when using SSL client auth?
 ### Why SSLv3 alert handshake failure when using SSL client auth?
@@ -677,7 +917,7 @@ Add the following section to your openssl.cnf:
 When creating the cert be sure to reference it in the `-extensions` flag:
 When creating the cert be sure to reference it in the `-extensions` flag:
 
 
 ```
 ```
-openssl ca -config openssl.cnf -policy policy_anything -extensions ssl_client -out certs/node.crt -infiles node.csr
+openssl ca -config openssl.cnf -policy policy_anything -extensions ssl_client -out certs/machine.crt -infiles machine.csr
 ```
 ```
 
 
 
 

+ 2 - 0
error/error.go

@@ -32,6 +32,7 @@ const (
 	EcodeNotDir         = 104
 	EcodeNotDir         = 104
 	EcodeNodeExist      = 105
 	EcodeNodeExist      = 105
 	EcodeKeyIsPreserved = 106
 	EcodeKeyIsPreserved = 106
+	EcodeRootROnly      = 107
 
 
 	EcodeValueRequired      = 200
 	EcodeValueRequired      = 200
 	EcodePrevValueRequired  = 201
 	EcodePrevValueRequired  = 201
@@ -56,6 +57,7 @@ func init() {
 	errors[EcodeNoMorePeer] = "Reached the max number of peers in the cluster"
 	errors[EcodeNoMorePeer] = "Reached the max number of peers in the cluster"
 	errors[EcodeNotDir] = "Not A Directory"
 	errors[EcodeNotDir] = "Not A Directory"
 	errors[EcodeNodeExist] = "Already exists" // create
 	errors[EcodeNodeExist] = "Already exists" // create
+	errors[EcodeRootROnly] = "Root is read only"
 	errors[EcodeKeyIsPreserved] = "The prefix of given key is a keyword in etcd"
 	errors[EcodeKeyIsPreserved] = "The prefix of given key is a keyword in etcd"
 
 
 	// Post form related errors
 	// Post form related errors

+ 6 - 6
mod/lock/v2/acquire_handler.go

@@ -46,7 +46,7 @@ func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) {
 		http.Error(w, "add lock index error: " + err.Error(), http.StatusInternalServerError)
 		http.Error(w, "add lock index error: " + err.Error(), http.StatusInternalServerError)
 		return
 		return
 	}
 	}
-	indexpath := resp.Key
+	indexpath := resp.Node.Key
 
 
 	// Keep updating TTL to make sure lock request is not expired before acquisition.
 	// Keep updating TTL to make sure lock request is not expired before acquisition.
 	stop := make(chan bool)
 	stop := make(chan bool)
@@ -64,19 +64,19 @@ func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) {
 	}()
 	}()
 
 
 	// Extract the lock index.
 	// Extract the lock index.
-	index, _ := strconv.Atoi(path.Base(resp.Key))
+	index, _ := strconv.Atoi(path.Base(resp.Node.Key))
 
 
 	// Wait until we successfully get a lock or we get a failure.
 	// Wait until we successfully get a lock or we get a failure.
 	var success bool
 	var success bool
 	for {
 	for {
 		// Read all indices.
 		// Read all indices.
-		resp, err = h.client.GetAll(keypath, true)
+		resp, err = h.client.Get(keypath, true, true)
 		if err != nil {
 		if err != nil {
 			http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError)
 			http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError)
 			break
 			break
 		}
 		}
 		indices := extractResponseIndices(resp)
 		indices := extractResponseIndices(resp)
-		waitIndex := resp.ModifiedIndex
+		waitIndex := resp.Node.ModifiedIndex
 		prevIndex := findPrevIndex(indices, index)
 		prevIndex := findPrevIndex(indices, index)
 
 
 		// If there is no previous index then we have the lock.
 		// If there is no previous index then we have the lock.
@@ -86,7 +86,7 @@ func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) {
 		}
 		}
 
 
 		// Otherwise watch previous index until it's gone.
 		// Otherwise watch previous index until it's gone.
-		_, err = h.client.Watch(path.Join(keypath, strconv.Itoa(prevIndex)), waitIndex, nil, stopWatchChan)
+		_, err = h.client.Watch(path.Join(keypath, strconv.Itoa(prevIndex)), waitIndex, false, nil, stopWatchChan)
 		if err == etcd.ErrWatchStoppedByUser {
 		if err == etcd.ErrWatchStoppedByUser {
 			break
 			break
 		} else if err != nil {
 		} else if err != nil {
@@ -111,7 +111,7 @@ func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) {
 		w.Write([]byte(strconv.Itoa(index)))
 		w.Write([]byte(strconv.Itoa(index)))
 	} else {
 	} else {
 		// Make sure key is deleted if we couldn't acquire.
 		// Make sure key is deleted if we couldn't acquire.
-		h.client.Delete(indexpath)
+		h.client.Delete(indexpath, false)
 	}
 	}
 }
 }
 
 

+ 1 - 1
mod/lock/v2/get_index_handler.go

@@ -16,7 +16,7 @@ func (h *handler) getIndexHandler(w http.ResponseWriter, req *http.Request) {
 	keypath := path.Join(prefix, vars["key"])
 	keypath := path.Join(prefix, vars["key"])
 
 
 	// Read all indices.
 	// Read all indices.
-	resp, err := h.client.GetAll(keypath, true)
+	resp, err := h.client.Get(keypath, true, true)
 	if err != nil {
 	if err != nil {
 		http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError)
 		http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError)
 		return
 		return

+ 2 - 2
mod/lock/v2/handler.go

@@ -36,8 +36,8 @@ func NewHandler(addr string) (http.Handler) {
 // extractResponseIndices extracts a sorted list of indicies from a response.
 // extractResponseIndices extracts a sorted list of indicies from a response.
 func extractResponseIndices(resp *etcd.Response) []int {
 func extractResponseIndices(resp *etcd.Response) []int {
 	var indices []int
 	var indices []int
-	for _, kv := range resp.Kvs {
-		if index, _ := strconv.Atoi(path.Base(kv.Key)); index > 0 {
+	for _, node := range resp.Node.Nodes {
+		if index, _ := strconv.Atoi(path.Base(node.Key)); index > 0 {
 			indices = append(indices, index)
 			indices = append(indices, index)
 		}
 		}
 	}
 	}

+ 1 - 1
mod/lock/v2/release_handler.go

@@ -15,7 +15,7 @@ func (h *handler) releaseLockHandler(w http.ResponseWriter, req *http.Request) {
 	keypath := path.Join(prefix, vars["key_with_index"])
 	keypath := path.Join(prefix, vars["key_with_index"])
 
 
 	// Delete the lock.
 	// Delete the lock.
-	_, err := h.client.Delete(keypath)
+	_, err := h.client.Delete(keypath, false)
 	if err != nil {
 	if err != nil {
 		http.Error(w, "delete lock index error: " + err.Error(), http.StatusInternalServerError)
 		http.Error(w, "delete lock index error: " + err.Error(), http.StatusInternalServerError)
 		return
 		return

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

+ 5 - 0
server/server.go

@@ -96,6 +96,11 @@ func (s *Server) PeerURL(name string) (string, bool) {
 	return s.registry.PeerURL(name)
 	return s.registry.PeerURL(name)
 }
 }
 
 
+// ClientURL retrieves the Client URL for a given node name.
+func (s *Server) ClientURL(name string) (string, bool) {
+	return s.registry.ClientURL(name)
+}
+
 // Returns a reference to the Store.
 // Returns a reference to the Store.
 func (s *Server) Store() store.Store {
 func (s *Server) Store() store.Store {
 	return s.store
 	return s.store

+ 3 - 2
server/usage.go

@@ -26,8 +26,9 @@ Options:
   -vv               Enabled very verbose logging.
   -vv               Enabled very verbose logging.
 
 
 Cluster Configuration Options:
 Cluster Configuration Options:
-  -peers=<peers>      Comma-separated list of peers (ip + port) in the cluster.
-  -peers-file=<path>  Path to a file containing the peer list.
+  -peers-file=<path>              Path to a file containing the peer list.
+  -peers=<host:port>,<host:port>  Comma-separated list of peers. The members
+                                  should match the peer's '-peer-addr' flag.
 
 
 Client Communication Options:
 Client Communication Options:
   -addr=<host:port>   The public host:port used for client communication.
   -addr=<host:port>   The public host:port used for client communication.

+ 1 - 1
server/v2/get_handler.go

@@ -23,7 +23,7 @@ func GetHandler(w http.ResponseWriter, req *http.Request, s Server) error {
 	// Help client to redirect the request to the current leader
 	// Help client to redirect the request to the current leader
 	if req.FormValue("consistent") == "true" && s.State() != raft.Leader {
 	if req.FormValue("consistent") == "true" && s.State() != raft.Leader {
 		leader := s.Leader()
 		leader := s.Leader()
-		hostname, _ := s.PeerURL(leader)
+		hostname, _ := s.ClientURL(leader)
 		url := hostname + req.URL.Path
 		url := hostname + req.URL.Path
 		log.Debugf("Redirect consistent get to %s", url)
 		log.Debugf("Redirect consistent get to %s", url)
 		http.Redirect(w, req, url, http.StatusTemporaryRedirect)
 		http.Redirect(w, req, url, http.StatusTemporaryRedirect)

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

@@ -24,6 +24,6 @@ func TestV2DeleteKey(t *testing.T) {
 		resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), url.Values{})
 		resp, err = tests.DeleteForm(fmt.Sprintf("%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":3}`, "")
+		assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo/bar","prevValue":"XXX","modifiedIndex":3,"createdIndex":2}}`, "")
 	})
 	})
 }
 }

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

@@ -25,9 +25,10 @@ func TestV2GetKey(t *testing.T) {
 		resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"))
 		resp, _ = tests.Get(fmt.Sprintf("%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"], 2, "")
+		node := body["node"].(map[string]interface{})
+		assert.Equal(t, node["key"], "/foo/bar", "")
+		assert.Equal(t, node["value"], "XXX", "")
+		assert.Equal(t, node["modifiedIndex"], 2, "")
 	})
 	})
 }
 }
 
 
@@ -52,23 +53,24 @@ func TestV2GetKeyRecursively(t *testing.T) {
 		resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?recursive=true"))
 		resp, _ = tests.Get(fmt.Sprintf("%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"], 2, "")
-		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"], 2, "")
+		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 +111,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"], 2, "")
+
+		node := body["node"].(map[string]interface{})
+		assert.Equal(t, node["key"], "/foo/bar", "")
+		assert.Equal(t, node["value"], "XXX", "")
+		assert.Equal(t, node["modifiedIndex"], 2, "")
 	})
 	})
 }
 }
 
 
@@ -162,8 +166,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"], 3, "")
+
+		node := body["node"].(map[string]interface{})
+		assert.Equal(t, node["key"], "/foo/bar", "")
+		assert.Equal(t, node["value"], "YYY", "")
+		assert.Equal(t, node["modifiedIndex"], 3, "")
 	})
 	})
 }
 }

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

@@ -21,18 +21,24 @@ func TestV2CreateUnique(t *testing.T) {
 		resp, _ := tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
 		resp, _ := tests.PostForm(fmt.Sprintf("%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/2", "")
-		assert.Equal(t, body["dir"], true, "")
-		assert.Equal(t, body["modifiedIndex"], 2, "")
+
+		node := body["node"].(map[string]interface{})
+		assert.Equal(t, node["key"], "/foo/bar/2", "")
+		assert.Equal(t, node["dir"], true, "")
+		assert.Equal(t, node["modifiedIndex"], 2, "")
 
 
 		// Second POST should add next index to list.
 		// Second POST should add next index to list.
 		resp, _ = tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
 		resp, _ = tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), nil)
 		body = tests.ReadBodyJSON(resp)
 		body = tests.ReadBodyJSON(resp)
-		assert.Equal(t, body["key"], "/foo/bar/3", "")
+
+		node = body["node"].(map[string]interface{})
+		assert.Equal(t, node["key"], "/foo/bar/3", "")
 
 
 		// 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("%s%s", s.URL(), "/v2/keys/foo/baz"), nil)
 		resp, _ = tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/baz"), nil)
 		body = tests.ReadBodyJSON(resp)
 		body = tests.ReadBodyJSON(resp)
-		assert.Equal(t, body["key"], "/foo/baz/4", "")
+
+		node = body["node"].(map[string]interface{})
+		assert.Equal(t, node["key"], "/foo/baz/4", "")
 	})
 	})
 }
 }

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

@@ -22,7 +22,7 @@ func TestV2SetKey(t *testing.T) {
 		resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		resp, err := tests.PutForm(fmt.Sprintf("%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":2}`, "")
+		assert.Equal(t, string(body), `{"action":"set","node":{"key":"/foo/bar","value":"XXX","modifiedIndex":2,"createdIndex":2}}`, "")
 	})
 	})
 }
 }
 
 
@@ -38,10 +38,11 @@ func TestV2SetKeyWithTTL(t *testing.T) {
 		v.Set("ttl", "20")
 		v.Set("ttl", "20")
 		resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		resp, _ := tests.PutForm(fmt.Sprintf("%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("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		resp, _ := tests.PutForm(fmt.Sprintf("%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("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		resp, _ = tests.PutForm(fmt.Sprintf("%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,10 @@ func TestV2SetKeyCASOnIndexSuccess(t *testing.T) {
 		resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		resp, _ = tests.PutForm(fmt.Sprintf("%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"], 3, "")
+		node := body["node"].(map[string]interface{})
+		assert.Equal(t, node["prevValue"], "XXX", "")
+		assert.Equal(t, node["value"], "YYY", "")
+		assert.Equal(t, node["modifiedIndex"], 3, "")
 	})
 	})
 }
 }
 
 
@@ -234,9 +239,10 @@ func TestV2SetKeyCASOnValueSuccess(t *testing.T) {
 		resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
 		resp, _ = tests.PutForm(fmt.Sprintf("%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"], 3, "")
+		node := body["node"].(map[string]interface{})
+		assert.Equal(t, node["prevValue"], "XXX", "")
+		assert.Equal(t, node["value"], "YYY", "")
+		assert.Equal(t, node["modifiedIndex"], 3, "")
 	})
 	})
 }
 }
 
 

+ 1 - 0
server/v2/v2.go

@@ -13,6 +13,7 @@ type Server interface {
 	CommitIndex() uint64
 	CommitIndex() uint64
 	Term() uint64
 	Term() uint64
 	PeerURL(string) (string, bool)
 	PeerURL(string) (string, bool)
+	ClientURL(string) (string, bool)
 	Store() store.Store
 	Store() store.Store
 	Dispatch(raft.Command, http.ResponseWriter, *http.Request) error
 	Dispatch(raft.Command, http.ResponseWriter, *http.Request) error
 }
 }

+ 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

+ 17 - 4
store/event_history.go

@@ -2,6 +2,7 @@ package store
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"path"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 
 
@@ -33,14 +34,14 @@ 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
 }
 }
 
 
 // scan function is enumerating events from the index in history and
 // scan function is enumerating events from the index in history and
-// stops till the first point where the key has identified prefix
-func (eh *EventHistory) scan(prefix string, index uint64) (*Event, *etcdErr.Error) {
+// stops till the first point where the key has identified key
+func (eh *EventHistory) scan(key string, recursive bool, index uint64) (*Event, *etcdErr.Error) {
 	eh.rwl.RLock()
 	eh.rwl.RLock()
 	defer eh.rwl.RUnlock()
 	defer eh.rwl.RUnlock()
 
 
@@ -62,7 +63,19 @@ func (eh *EventHistory) scan(prefix string, index uint64) (*Event, *etcdErr.Erro
 	for {
 	for {
 		e := eh.Queue.Events[i]
 		e := eh.Queue.Events[i]
 
 
-		if strings.HasPrefix(e.Key, prefix) && index <= e.Index() { // make sure we bypass the smaller one
+		ok := (e.Node.Key == key)
+
+		if recursive {
+			// add tailing slash
+			key := path.Clean(key)
+			if key[len(key)-1] != '/' {
+				key = key + "/"
+			}
+
+			ok = ok || strings.HasPrefix(e.Node.Key, key)
+		}
+
+		if ok && index <= e.Index() { // make sure we bypass the smaller one
 			return e, nil
 			return e, nil
 		}
 		}
 
 

+ 10 - 10
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,30 +35,30 @@ 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", 1)
+	e, err := eh.scan("/foo", false, 1)
 	if err != nil || e.Index() != 1 {
 	if err != nil || e.Index() != 1 {
 		t.Fatalf("scan error [/foo] [1] %v", e.Index)
 		t.Fatalf("scan error [/foo] [1] %v", e.Index)
 	}
 	}
 
 
-	e, err = eh.scan("/foo/bar", 1)
+	e, err = eh.scan("/foo/bar", false, 1)
 
 
 	if err != nil || e.Index() != 2 {
 	if err != nil || e.Index() != 2 {
 		t.Fatalf("scan error [/foo/bar] [2] %v", e.Index)
 		t.Fatalf("scan error [/foo/bar] [2] %v", e.Index)
 	}
 	}
 
 
-	e, err = eh.scan("/foo/bar", 3)
+	e, err = eh.scan("/foo/bar", true, 3)
 
 
 	if err != nil || e.Index() != 4 {
 	if err != nil || e.Index() != 4 {
 		t.Fatalf("scan error [/foo/bar/bar] [4] %v", e.Index)
 		t.Fatalf("scan error [/foo/bar/bar] [4] %v", e.Index)
 	}
 	}
 
 
-	e, err = eh.scan("/foo/bar", 6)
+	e, err = eh.scan("/foo/bar", true, 6)
 
 
 	if e != nil {
 	if e != nil {
 		t.Fatalf("bad index shoud reuturn nil")
 		t.Fatalf("bad index shoud reuturn nil")

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

+ 64 - 45
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,34 +131,32 @@ 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) {
-	nodePath = path.Clean(path.Join("/", nodePath))
-
 	s.worldLock.Lock()
 	s.worldLock.Lock()
 	defer s.worldLock.Unlock()
 	defer s.worldLock.Unlock()
 	e, err := s.internalCreate(nodePath, value, unique, false, expireTime, Create)
 	e, err := s.internalCreate(nodePath, value, unique, false, expireTime, Create)
@@ -171,10 +170,8 @@ 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) {
-	nodePath = path.Clean(path.Join("/", nodePath))
-
 	s.worldLock.Lock()
 	s.worldLock.Lock()
 	defer s.worldLock.Unlock()
 	defer s.worldLock.Unlock()
 	e, err := s.internalCreate(nodePath, value, false, true, expireTime, Set)
 	e, err := s.internalCreate(nodePath, value, false, true, expireTime, Set)
@@ -192,6 +189,10 @@ func (s *store) CompareAndSwap(nodePath string, prevValue string, prevIndex uint
 	value string, expireTime time.Time) (*Event, error) {
 	value string, expireTime time.Time) (*Event, error) {
 
 
 	nodePath = path.Clean(path.Join("/", nodePath))
 	nodePath = path.Clean(path.Join("/", nodePath))
+	// we do not allow the user to change "/"
+	if nodePath == "/" {
+		return nil, etcdErr.NewError(etcdErr.EcodeRootROnly, "/", s.CurrentIndex)
+	}
 
 
 	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)
@@ -238,6 +241,10 @@ func (s *store) CompareAndSwap(nodePath string, prevValue string, prevIndex uint
 // If the node is a directory, recursive must be true to delete it.
 // If the node is a directory, recursive must be true to delete it.
 func (s *store) Delete(nodePath string, recursive bool) (*Event, error) {
 func (s *store) Delete(nodePath string, recursive bool) (*Event, error) {
 	nodePath = path.Clean(path.Join("/", nodePath))
 	nodePath = path.Clean(path.Join("/", nodePath))
+	// we do not allow the user to change "/"
+	if nodePath == "/" {
+		return nil, etcdErr.NewError(etcdErr.EcodeRootROnly, "/", s.CurrentIndex)
+	}
 
 
 	s.worldLock.Lock()
 	s.worldLock.Lock()
 	defer s.worldLock.Unlock()
 	defer s.worldLock.Unlock()
@@ -251,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
@@ -280,8 +288,8 @@ func (s *store) Delete(nodePath string, recursive bool) (*Event, error) {
 	return e, nil
 	return e, nil
 }
 }
 
 
-func (s *store) Watch(prefix string, recursive bool, sinceIndex uint64) (<-chan *Event, error) {
-	prefix = path.Clean(path.Join("/", prefix))
+func (s *store) Watch(key string, recursive bool, sinceIndex uint64) (<-chan *Event, error) {
+	key = path.Clean(path.Join("/", key))
 
 
 	nextIndex := s.CurrentIndex + 1
 	nextIndex := s.CurrentIndex + 1
 
 
@@ -292,10 +300,10 @@ func (s *store) Watch(prefix string, recursive bool, sinceIndex uint64) (<-chan
 	var err *etcdErr.Error
 	var err *etcdErr.Error
 
 
 	if sinceIndex == 0 {
 	if sinceIndex == 0 {
-		c, err = s.WatcherHub.watch(prefix, recursive, nextIndex)
+		c, err = s.WatcherHub.watch(key, recursive, nextIndex)
 
 
 	} else {
 	} else {
-		c, err = s.WatcherHub.watch(prefix, recursive, sinceIndex)
+		c, err = s.WatcherHub.watch(key, recursive, sinceIndex)
 	}
 	}
 
 
 	if err != nil {
 	if err != nil {
@@ -309,7 +317,7 @@ func (s *store) Watch(prefix string, recursive bool, sinceIndex uint64) (<-chan
 }
 }
 
 
 // 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
@@ -334,13 +342,17 @@ func (s *store) walk(nodePath string, walkFunc func(prev *Node, component string
 // If the node is a file, the value and the ttl can be updated.
 // If the node is a file, the value and the ttl can be updated.
 // If the node is a directory, only the ttl can be updated.
 // If the node is a directory, only the ttl can be updated.
 func (s *store) Update(nodePath string, newValue string, expireTime time.Time) (*Event, error) {
 func (s *store) Update(nodePath string, newValue string, expireTime time.Time) (*Event, error) {
+	nodePath = path.Clean(path.Join("/", nodePath))
+	// we do not allow the user to change "/"
+	if nodePath == "/" {
+		return nil, etcdErr.NewError(etcdErr.EcodeRootROnly, "/", s.CurrentIndex)
+	}
+
 	s.worldLock.Lock()
 	s.worldLock.Lock()
 	defer s.worldLock.Unlock()
 	defer s.worldLock.Unlock()
 
 
 	currIndex, nextIndex := s.CurrentIndex, s.CurrentIndex+1
 	currIndex, nextIndex := s.CurrentIndex, s.CurrentIndex+1
 
 
-	nodePath = path.Clean(path.Join("/", nodePath))
-
 	n, err := s.internalGet(nodePath)
 	n, err := s.internalGet(nodePath)
 
 
 	if err != nil { // if the node does not exist, return error
 	if err != nil { // if the node does not exist, return error
@@ -348,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() {
@@ -357,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)
 
 
@@ -390,13 +404,17 @@ func (s *store) internalCreate(nodePath string, value string, unique bool, repla
 
 
 	nodePath = path.Clean(path.Join("/", nodePath))
 	nodePath = path.Clean(path.Join("/", nodePath))
 
 
+	// we do not allow the user to change "/"
+	if nodePath == "/" {
+		return nil, etcdErr.NewError(etcdErr.EcodeRootROnly, "/", currIndex)
+	}
+
 	// Assume expire times that are way in the past are not valid.
 	// Assume expire times that are way in the past are not valid.
 	// This can occur when the time is serialized to JSON and read back in.
 	// This can occur when the time is serialized to JSON and read back in.
 	if expireTime.Before(minExpireTime) {
 	if expireTime.Before(minExpireTime) {
 		expireTime = Permanent
 		expireTime = Permanent
 	}
 	}
 
 
-
 	dir, newNodeName := path.Split(nodePath)
 	dir, newNodeName := path.Split(nodePath)
 
 
 	// walk through the nodePath, create dirs and get the last directory node
 	// walk through the nodePath, create dirs and get the last directory node
@@ -408,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)
 
 
@@ -418,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 {
@@ -427,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)
 
 
@@ -441,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
@@ -455,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)
@@ -498,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))
 	}
 	}
 
 
 }
 }
@@ -507,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 {

+ 82 - 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)
@@ -213,6 +213,26 @@ func TestStoreDeleteDiretory(t *testing.T) {
 	assert.Equal(t, e.Action, "delete", "")
 	assert.Equal(t, e.Action, "delete", "")
 }
 }
 
 
+func TestRootRdOnly(t *testing.T) {
+	s := newStore()
+
+	_, err := s.Set("/", "", Permanent)
+	assert.NotNil(t, err, "")
+
+	_, err = s.Delete("/", true)
+	assert.NotNil(t, err, "")
+
+	_, err = s.Create("/", "", false, Permanent)
+	assert.NotNil(t, err, "")
+
+	_, err = s.Update("/", "", Permanent)
+	assert.NotNil(t, err, "")
+
+	_, err = s.CompareAndSwap("/", "", 0, "", Permanent)
+	assert.NotNil(t, err, "")
+
+}
+
 // Ensure that the store cannot delete a directory if recursive is not specified.
 // Ensure that the store cannot delete a directory if recursive is not specified.
 func TestStoreDeleteDiretoryFailsIfNonRecursive(t *testing.T) {
 func TestStoreDeleteDiretoryFailsIfNonRecursive(t *testing.T) {
 	s := newStore()
 	s := newStore()
@@ -231,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.
@@ -247,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.
@@ -257,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.
@@ -273,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.
@@ -283,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, "")
 }
 }
@@ -295,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.
@@ -306,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.
@@ -317,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.
@@ -328,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.
@@ -339,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.
@@ -350,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.
@@ -361,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.
@@ -383,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.
@@ -403,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.
@@ -441,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)

+ 8 - 8
store/watcher_hub.go

@@ -33,11 +33,11 @@ func newWatchHub(capacity int) *watcherHub {
 }
 }
 
 
 // watch function returns an Event channel.
 // watch function returns an Event channel.
-// If recursive is true, the first change after index under prefix will be sent to the event channel.
-// If recursive is false, the first change after index at prefix will be sent to the event channel.
+// If recursive is true, the first change after index under key will be sent to the event channel.
+// If recursive is false, the first change after index at key will be sent to the event channel.
 // If index is zero, watch will start from the current index + 1.
 // If index is zero, watch will start from the current index + 1.
-func (wh *watcherHub) watch(prefix string, recursive bool, index uint64) (<-chan *Event, *etcdErr.Error) {
-	event, err := wh.EventHistory.scan(prefix, index)
+func (wh *watcherHub) watch(key string, recursive bool, index uint64) (<-chan *Event, *etcdErr.Error) {
+	event, err := wh.EventHistory.scan(key, recursive, index)
 
 
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -57,7 +57,7 @@ func (wh *watcherHub) watch(prefix string, recursive bool, index uint64) (<-chan
 		sinceIndex: index,
 		sinceIndex: index,
 	}
 	}
 
 
-	l, ok := wh.watchers[prefix]
+	l, ok := wh.watchers[key]
 
 
 	if ok { // add the new watcher to the back of the list
 	if ok { // add the new watcher to the back of the list
 		l.PushBack(w)
 		l.PushBack(w)
@@ -65,7 +65,7 @@ func (wh *watcherHub) watch(prefix string, recursive bool, index uint64) (<-chan
 	} else { // create a new list and add the new watcher
 	} else { // create a new list and add the new watcher
 		l := list.New()
 		l := list.New()
 		l.PushBack(w)
 		l.PushBack(w)
-		wh.watchers[prefix] = l
+		wh.watchers[key] = l
 	}
 	}
 
 
 	atomic.AddInt64(&wh.count, 1)
 	atomic.AddInt64(&wh.count, 1)
@@ -77,7 +77,7 @@ func (wh *watcherHub) watch(prefix string, recursive bool, index uint64) (<-chan
 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

+ 23 - 3
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,27 @@ func TestWatcher(t *testing.T) {
 		// do nothing
 		// do nothing
 	}
 	}
 
 
-	e = newEvent(Create, "/foo", 3)
+	e = newEvent(Create, "/foo", 3, 3)
+
+	wh.notify(e)
+
+	re = <-c
+
+	if e != re {
+		t.Fatal("recv != send")
+	}
+
+	// ensure we are doing exact matching rather than prefix matching
+	c, _ = wh.watch("/fo", true, 1)
+
+	select {
+	case re = <-c:
+		t.Fatal("should not receive from channel:", re)
+	default:
+		// do nothing
+	}
+
+	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}}`)
 }
 }

+ 5 - 0
tests/mock/server_v2.go

@@ -45,6 +45,11 @@ func (s *ServerV2) PeerURL(name string) (string, bool) {
 	return args.String(0), args.Bool(1)
 	return args.String(0), args.Bool(1)
 }
 }
 
 
+func (s *ServerV2) ClientURL(name string) (string, bool) {
+	args := s.Called(name)
+	return args.String(0), args.Bool(1)
+}
+
 func (s *ServerV2) Store() store.Store {
 func (s *ServerV2) Store() store.Store {
 	return s.store
 	return s.store
 }
 }

+ 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" + err.Error())
-			}
-			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: {},
 		}
 		}
 	}
 	}