Browse Source

Merge branch 'master' of https://github.com/coreos/etcd into proxy

Conflicts:
	config/config.go
	server/peer_server.go
	server/transporter.go
	tests/server_utils.go
Ben Johnson 11 years ago
parent
commit
62b89a128a
67 changed files with 1540 additions and 1315 deletions
  1. 1 0
      .gitignore
  2. 6 0
      CONTRIBUTING.md
  3. 45 12
      Documentation/api.md
  4. 4 4
      Documentation/configuration.md
  5. 2 2
      Documentation/debugging.md
  6. 1 1
      Documentation/design/discovery.md
  7. 12 0
      Documentation/development-tools.md
  8. 1 1
      Documentation/discovery-protocol.md
  9. 2 0
      Documentation/errorcode.md
  10. 1 1
      Documentation/etcd-file-system.md
  11. 1 1
      Documentation/internal-protocol-versioning.md
  12. 12 1
      Documentation/libraries-and-tools.md
  13. 4 4
      Documentation/platforms/freebsd.md
  14. 1 0
      Documentation/security.md
  15. 15 15
      Documentation/tuning.md
  16. 4 2
      README.md
  17. 27 0
      Vagrantfile
  18. 39 37
      config/config.go
  19. 1 1
      config/timeout.go
  20. 12 46
      etcd.go
  21. 7 15
      fixtures/ca/README
  22. 0 21
      fixtures/ca/broken/README
  23. 0 33
      fixtures/ca/broken/ca.crt
  24. 0 51
      fixtures/ca/broken/ca.key
  25. 0 29
      fixtures/ca/broken/server.crt
  26. 0 27
      fixtures/ca/broken/server.csr
  27. 0 51
      fixtures/ca/broken/server.key
  28. 0 51
      fixtures/ca/broken/server.key.insecure
  29. 0 14
      fixtures/ca/broken/server.pub
  30. BIN
      fixtures/ca/broken/server.pub.sig
  31. 30 0
      fixtures/ca/broken_ca.crt
  32. 54 0
      fixtures/ca/broken_ca.key
  33. 31 0
      fixtures/ca/broken_server.crt
  34. 51 0
      fixtures/ca/broken_server.key.insecure
  35. 28 29
      fixtures/ca/ca.crt
  36. 50 50
      fixtures/ca/ca.key
  37. 58 0
      fixtures/ca/generate_testing_certs.sh
  38. 0 337
      fixtures/ca/openssl.cnf
  39. 57 59
      fixtures/ca/server-chain.pem
  40. 29 30
      fixtures/ca/server.crt
  41. 0 30
      fixtures/ca/server.csr
  42. 0 54
      fixtures/ca/server.key
  43. 49 49
      fixtures/ca/server.key.insecure
  44. 29 29
      fixtures/ca/server2.crt
  45. 0 29
      fixtures/ca/server2.csr
  46. 0 54
      fixtures/ca/server2.key
  47. 49 49
      fixtures/ca/server2.key.insecure
  48. 4 9
      mod/lock/v2/acquire_handler.go
  49. 7 4
      mod/lock/v2/lock_nodes.go
  50. 28 2
      server/listener.go
  51. 18 6
      server/peer_server.go
  52. 48 30
      server/transporter.go
  53. 2 2
      server/usage.go
  54. 3 4
      server/v1/tests/get_handler_test.go
  55. 1 1
      server/v1/tests/put_handler_test.go
  56. 3 4
      server/v2/tests/get_handler_test.go
  57. 80 2
      server/v2/tests/put_handler_test.go
  58. 25 5
      store/node.go
  59. 16 4
      store/store.go
  60. 2 2
      tests/functional/etcd_tls_test.go
  61. 78 2
      tests/functional/multi_node_kill_all_and_recovery_test.go
  62. 1 1
      tests/http_utils.go
  63. 12 18
      tests/server_utils.go
  64. 17 0
      third_party/github.com/mreiferson/go-httpclient/LICENSE
  65. 39 0
      third_party/github.com/mreiferson/go-httpclient/README.md
  66. 210 0
      third_party/github.com/mreiferson/go-httpclient/httpclient.go
  67. 233 0
      third_party/github.com/mreiferson/go-httpclient/httpclient_test.go

+ 1 - 0
.gitignore

@@ -3,3 +3,4 @@
 /machine*
 /bin
 /src
+.vagrant

+ 6 - 0
CONTRIBUTING.md

@@ -28,6 +28,12 @@ This is a rough outline of what a contributor's workflow looks like:
 
 Thanks for you contributions!
 
+### Code style
+
+The coding style suggested by the Golang community is used in etcd. See [style doc](https://code.google.com/p/go-wiki/wiki/Style) for details.
+
+Please follow this style to make etcd easy to review, maintain and develop.
+
 ### Format of the commit message
 
 etcd follow a rough convention for commit messages borrowed from Angularjs.

+ 45 - 12
Documentation/api.md

@@ -21,7 +21,7 @@ The key space consists of directories and keys which are generically referred to
 
 ### Setting the value of a key
 
-Lets set the first key-value pair in the datastore.
+Let's set the first key-value pair in the datastore.
 In this case the key is `/message` and the value is `Hello world`.
 
 ```sh
@@ -118,11 +118,10 @@ curl -L http://127.0.0.1:4001/v2/keys/message -XPUT -d value="Hello etcd"
         "value": "Hello etcd"
     },
     "prevNode": {
-    	"createdIndex":2
+    	"createdIndex": 2
     	"key": "/message",
     	"value": "Hello world",
     	"modifiedIndex": 2,
-    
     }
 }
 ```
@@ -379,11 +378,11 @@ curl 'http://127.0.0.1:4001/v2/keys/dir/asdf?consistent=true&wait=true'
 		"modifiedIndex": 15
 	},
 	"prevNode": {
-		"createdIndex":8,
+		"createdIndex": 8,
 		"key": "/dir",
 		"dir":true,
 		"modifiedIndex": 17,
-		"expiration":"2013-12-11T10:39:35.689275857-08:00"
+		"expiration": "2013-12-11T10:39:35.689275857-08:00"
 	},
 }
 ```
@@ -438,14 +437,15 @@ This will try to compare the previous value of the key and the previous value we
 
 ```json
 {
-    "cause": "[two != one] [0 != 8]",
+    "cause": "[two != one]",
     "errorCode": 101,
     "index": 8,
     "message": "Test Failed"
 }
 ```
 
-which means `CompareAndSwap` failed.
+which means `CompareAndSwap` failed. `cause` explains why the test failed.
+Note: the condition prevIndex=0 always passes.
 
 Let's try a valid condition:
 
@@ -465,7 +465,7 @@ The response should be:
         "value": "two"
     },
     "prevNode": {
-    	"createdIndex":8,
+    	"createdIndex": 8,
     	"key": "/foo",
     	"modifiedIndex": 8,
     	"value": "one"
@@ -504,7 +504,7 @@ The error code explains the problem:
 {
 	"errorCode": 101,
 	"message": "Compare failed",
-	"cause": "[two != one] [0 != 8]",
+	"cause": "[two != one]",
 	"index": 8
 }
 ```
@@ -519,7 +519,7 @@ curl -L http://127.0.0.1:4001/v2/keys/foo?prevIndex=1 -XDELETE
 {
 	"errorCode": 101,
 	"message": "Compare failed",
-	"cause": "[ != one] [1 != 8]",
+	"cause": "[1 != 8]",
 	"index": 8
 }
 ```
@@ -783,6 +783,39 @@ curl -L http://127.0.0.1:4001/v2/keys/
 
 Here we see the `/message` key but our hidden `/_message` key is not returned.
 
+### Setting a key from a file
+
+You can also use etcd to store small configuration files, json documents, XML documents, etc directly.
+For example you can use curl to upload a simple text file and encode it:
+
+```
+echo "Hello\nWorld" > afile.txt
+curl -L http://127.0.0.1:4001/v2/keys/afile -XPUT --data-urlencode value@afile.txt
+```
+
+```json
+{
+    "action": "get",
+    "node": {
+        "createdIndex": 2,
+        "key": "/afile",
+        "modifiedIndex": 2,
+        "value": "Hello\nWorld\n"
+    }
+}
+```
+
+### Read Consistency
+
+Followers in a cluster can be behind the leader in their copy of the keyspace.
+If your application wants or needs the most up-to-date version of a key then it should ensure it reads from the current leader.
+By using the `consistent=true` flag in your GET requests, etcd will make sure you are talking to the current master.
+
+As an example of how a machine can be behind the leader let's start with a three machine cluster: L, F1, and F2.
+A client makes a write to L and F1 acknowledges the request.
+The client is told the write was successful and the keyspace is updated.
+Meanwhile F2 has partitioned from the network and will have an out-of-date version of the keyspace until the partition resolves.
+Since F2 missed the most recent write, a client reading from F2 will have an out-of-date version of the keyspace.
 
 ## Lock Module
 
@@ -1056,7 +1089,7 @@ Each node keeps a number of internal statistics:
 - `sendAppendRequestCnt`: number of requests that this node has sent
 - `sendBandwidthRate`: number of bytes per second this node is receiving (leader only). This value is undefined on single machine clusters.
 - `sendPkgRate`: number of requests per second this node is receiving (leader only). This value is undefined on single machine clusters.
-- `state`: either leader or folower
+- `state`: either leader or follower
 - `startTime`: the time when this node was started
 
 This is an example response from a follower machine:
@@ -1087,7 +1120,7 @@ And this is an example response from a leader machine:
 curl -L http://127.0.0.1:4001/v2/stats/self
 ```
 
-```
+```json
 {
     "leaderInfo": {
         "leader": "machine0",

+ 4 - 4
Documentation/configuration.md

@@ -20,21 +20,21 @@ configuration files.
 
 * `-addr` - The advertised public hostname:port for client communication. Defaults to `127.0.0.1:4001`.
 * `-discovery` - A URL to use for discovering the peer list. (i.e `"https://discovery.etcd.io/your-unique-key"`).
-* `-bind-addr` - The listening hostname for client communication. Defaults to advertised ip.
+* `-bind-addr` - The listening hostname for client communication. Defaults to advertised IP.
 * `-peers` - A comma separated list of peers in the cluster (i.e `"203.0.113.101:7001,203.0.113.102:7001"`).
 * `-peers-file` - The file path containing a comma separated list of peers in the cluster.
 * `-ca-file` - The path of the client CAFile. Enables client cert authentication when present.
 * `-cert-file` - The cert file of the client.
 * `-key-file` - The key file of the client.
-* `-config` - The path of the etcd config file. Defaults to `/etc/etcd/etcd.conf`.
+* `-config` - The path of the etcd configuration file. Defaults to `/etc/etcd/etcd.conf`.
 * `-cors` - A comma separated white list of origins for cross-origin resource sharing.
-* `-cpuprofile` - The path to a file to output cpu profile data. Enables cpu profiling when present.
+* `-cpuprofile` - The path to a file to output CPU profile data. Enables CPU profiling when present.
 * `-data-dir` - The directory to store log and snapshot. Defaults to the current working directory.
 * `-max-result-buffer` - The max size of result buffer. Defaults to `1024`.
 * `-max-cluster-size` - The max size of the cluster. Defaults to `9`.
 * `-max-retry-attempts` - The max retry attempts when trying to join a cluster. Defaults to `3`.
 * `-peer-addr` - The advertised public hostname:port for server communication. Defaults to `127.0.0.1:7001`.
-* `-peer-bind-addr` - The listening hostname for server communication. Defaults to advertised ip.
+* `-peer-bind-addr` - The listening hostname for server communication. Defaults to advertised IP.
 * `-peer-ca-file` - The path of the CAFile. Enables client/peer cert authentication when present.
 * `-peer-cert-file` - The cert file of the server.
 * `-peer-key-file` - The key file of the server.

+ 2 - 2
Documentation/debugging.md

@@ -44,10 +44,10 @@ See an [example collectd deploy script](https://github.com/coreos/etcd/contrib/c
 ## Profiling
 
 etcd exposes profiling information from the Go pprof package over HTTP.
-The basic browseable interface is served by etcd at the `/debug/pprof` HTTP endpoint (i.e. `http://127.0.0.1:4001/debug/pprof`).
+The basic browsable interface is served by etcd at the `/debug/pprof` HTTP endpoint (i.e. `http://127.0.0.1:4001/debug/pprof`).
 For more information on using profiling tools, see http://blog.golang.org/profiling-go-programs.
 
-**NOTE**: In the following examples you need to ensure that the `./bin/etcd` is identical to the `./bin/etcd` that you are targetting (same git hash, arch, platform, etc).
+**NOTE**: In the following examples you need to ensure that the `./bin/etcd` is identical to the `./bin/etcd` that you are targeting (same git hash, arch, platform, etc).
 
 #### Heap memory profile
 

+ 1 - 1
Documentation/design/discovery.md

@@ -14,7 +14,7 @@ a new cluster. We assume the user doesn't want to start a brand new cluster with
 
 ## Logical Workflow
 
-Start a etcd machine:
+Start an etcd machine:
 
 ```
 If discovery url is given:

+ 12 - 0
Documentation/development-tools.md

@@ -0,0 +1,12 @@
+# Development tools
+
+## Vagrant
+
+For fast start you can use Vagrant. `vagrant up` will make etcd build and running on virtual machine. Required Vagrant version is 1.5.0.
+
+Next lets set a single key and then retrieve it:
+
+```
+curl -L http://127.0.0.1:4001/v2/keys/mykey -XPUT -d value="this is awesome"
+curl -L http://127.0.0.1:4001/v2/keys/mykey
+```

+ 1 - 1
Documentation/discovery-protocol.md

@@ -20,7 +20,7 @@ UUID=$(uuidgen)
 
 Now that you have your cluster ID you can start bringing up machines. Every machine will follow this protocol internally in etcd if given a `-discovery`.
 
-### Registering your Machine 
+### Registering your Machine
 
 The first thing etcd must do is register your machine. This is done by using the machine name (from the `-name` arg) and posting it with a long TTL to the given key.
 

+ 2 - 0
Documentation/errorcode.md

@@ -21,6 +21,7 @@ Error code corresponding strerror
         EcodeNotDir         = 104
         EcodeNodeExist      = 105
         EcodeKeyIsPreserved = 106
+        EcodeRootROnly      = 107
 
         EcodeValueRequired     = 200
         EcodePrevValueRequired = 201
@@ -42,6 +43,7 @@ Error code corresponding strerror
     errors[104] = "Not A Directory"
     errors[105] = "Already exists" // create
     errors[106] = "The prefix of given key is a keyword in etcd"
+    errors[107] = "Root is read only"
 
     // Post form related errors
     errors[200] = "Value is Required in POST form"

+ 1 - 1
Documentation/etcd-file-system.md

@@ -77,7 +77,7 @@ Unless overridden, a node naturally inherits the ACL names of its parent directo
 
 For each ACL name, it has three children: *R (Reading)*, *W (Writing)*, *C (Changing)*
 
-Each permission is also a node. Under the node it contains the users who have this permission for the file refering to this ACL name.
+Each permission is also a node. Under the node it contains the users who have this permission for the file referring to this ACL name.
 
 ### Example
 [TODO]

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

@@ -26,7 +26,7 @@ Advantages
 
 Disadvantages
 
-- Follower knows better what versions of the interal protocol it can talk than the leader
+- Follower knows better what versions of the internal protocol it can talk than the leader
 
 
 ### Follower Controlled

+ 12 - 1
Documentation/libraries-and-tools.md

@@ -4,6 +4,7 @@
 
 - [etcdctl](https://github.com/coreos/etcdctl) - A command line client for etcd
 - [etcd-dump](https://npmjs.org/package/etcd-dump) - Command line utility for dumping/restoring etcd.
+- [etcd-fs](https://github.com/xetorthio/etcd-fs) - FUSE filesystem for etcd
 
 **Go libraries**
 
@@ -46,7 +47,16 @@
 - [marshall-lee/etcd.erl](https://github.com/marshall-lee/etcd.erl)
 
 **.Net Libraries**
--[drusellers/etcetera](https://github.com/drusellers/etcetera)
+
+- [drusellers/etcetera](https://github.com/drusellers/etcetera)
+
+**PHP Libraries**
+
+- [linkorb/etcd-php](https://github.com/linkorb/etcd-php)
+
+**Haskell libraries**
+
+- [wereHamster/etcd-hs](https://github.com/wereHamster/etcd-hs)
 
 A detailed recap of client functionalities can be found in the [clients compatibility matrix][clients-matrix.md].
 
@@ -77,3 +87,4 @@ A detailed recap of client functionalities can be found in the [clients compatib
 - [mattn/etcdenv](https://github.com/mattn/etcdenv) - "env" shebang with etcd integration
 - [kelseyhightower/confd](https://github.com/kelseyhightower/confd) - Manage local app config files using templates and data from etcd
 - [configdb](https://git.autistici.org/ai/configdb/tree/master) - A REST relational abstraction on top of arbitrary database backends, aimed at storing configs and inventories.
+- [scrz](https://github.com/scrz/scrz) - Container manager, stores configuration in etcd.

+ 4 - 4
Documentation/platforms/freebsd.md

@@ -26,7 +26,7 @@ coreos­etcdctl­0.2.0           Simple commandline client for et
 r@fbsd­10:/ #
 ```
 
-5. You’re ready to use etcd and etcdctl! For more information about using pkgng, plese
+5. You’re ready to use etcd and etcdctl! For more information about using pkgng, please
 see: http://www.freebsd.org/doc/handbook/pkgng­intro.html
  
 ### Using ports system
@@ -37,7 +37,7 @@ may take some time depending on your hardware and network connection)
 2. Build etcd with `cd /usr/ports/devel/etcd && make install clean`, you
 will get an option to build and install documentation and etcdctl with it.
 
-3. If you havent install it with etcdctl, and you would like to install it later, you can build it
+3. If you haven't installed it with etcdctl, and you would like to install it later, you can build it
 with `cd /usr/ports/devel/etcdctl && make install clean`
 
 4. Verify successful installation with `pkg info | grep etcd` and you should get:
@@ -55,8 +55,8 @@ please see: https://www.freebsd.org/doc/handbook/ports­using.html
 
 ## Issues
 
-If you find any issues with the build/install procedure or youve found a problem that
-youve verified is local to FreeBSD version only (for example, by not being able to
+If you find any issues with the build/install procedure or you've found a problem that
+you've verified is local to FreeBSD version only (for example, by not being able to
 reproduce it on any other platform, like OSX or Linux), please sent a
 problem report using this page for more
 information: http://www.freebsd.org/send­pr.html

+ 1 - 0
Documentation/security.md

@@ -7,6 +7,7 @@ Etcd supports SSL/TLS and client cert authentication for clients to server, as w
 First, you need to have a CA cert `clientCA.crt` and signed key pair `client.crt`, `client.key`.
 This site has a good reference for how to generate self-signed key pairs:
 http://www.g-loaded.eu/2005/11/10/be-your-own-ca/
+Or you could use [etcd-ca](https://github.com/coreos/etcd-ca) to generate certs and keys.
 
 For testing you can use the certificates in the `fixtures/ca` directory.
 

+ 15 - 15
Documentation/tuning.md

@@ -1,46 +1,46 @@
 ## Tuning
 
 The default settings in etcd should work well for installations on a local network where the average network latency is low.
-However, when using etcd across multiple data centers or over networks with high latency you may need to tweak the heartbeat and election timeout settings.
+However, when using etcd across multiple data centers or over networks with high latency you may need to tweak the heartbeat interval and election timeout settings.
 
-### Timeouts
+### Time Parameters
 
-The underlying distributed consensus protocol relies on two separate timeouts to ensure that nodes can handoff leadership if one stalls or goes offline.
-The first timeout is called the *Heartbeat Timeout*.
+The underlying distributed consensus protocol relies on two separate time parameters to ensure that nodes can handoff leadership if one stalls or goes offline.
+The first parameter is called the *Heartbeat Interval*.
 This is the frequency with which the leader will notify followers that it is still the leader.
-etcd batches commands together for higher throughput so this heartbeat timeout is also a delay for how long it takes for commands to be committed.
-By default, etcd uses a `50ms` heartbeat timeout.
+etcd batches commands together for higher throughput so this heartbeat interval is also a delay for how long it takes for commands to be committed.
+By default, etcd uses a `50ms` heartbeat interval.
 
-The second timeout is the *Election Timeout*.
+The second parameter is the *Election Timeout*.
 This timeout is how long a follower node will go without hearing a heartbeat before attempting to become leader itself.
 By default, etcd uses a `200ms` election timeout.
 
 Adjusting these values is a trade off.
-Lowering the heartbeat timeout will cause individual commands to be committed faster but it will lower the overall throughput of etcd.
-If your etcd instances have low utilization then lowering the heartbeat timeout can improve your command response time.
+Lowering the heartbeat interval will cause individual commands to be committed faster but it will lower the overall throughput of etcd.
+If your etcd instances have low utilization then lowering the heartbeat interval can improve your command response time.
 
-The election timeout should be set based on the heartbeat timeout and your network ping time between nodes.
+The election timeout should be set based on the heartbeat interval and your network ping time between nodes.
 Election timeouts should be at least 10 times your ping time so it can account for variance in your network.
 For example, if the ping time between your nodes is 10ms then you should have at least a 100ms election timeout.
 
-You should also set your election timeout to at least 4 to 5 times your heartbeat timeout to account for variance in leader replication.
-For a heartbeat timeout of 50ms you should set your election timeout to at least 200ms - 250ms.
+You should also set your election timeout to at least 4 to 5 times your heartbeat interval to account for variance in leader replication.
+For a heartbeat interval of 50ms you should set your election timeout to at least 200ms - 250ms.
 
 You can override the default values on the command line:
 
 ```sh
 # Command line arguments:
-$ etcd -peer-heartbeat-timeout=100 -peer-election-timeout=500
+$ etcd -peer-heartbeat-interval=100 -peer-election-timeout=500
 
 # Environment variables:
-$ ETCD_PEER_HEARTBEAT_TIMEOUT=100 ETCD_PEER_ELECTION_TIMEOUT=500 etcd
+$ ETCD_PEER_HEARTBEAT_INTERVAL=100 ETCD_PEER_ELECTION_TIMEOUT=500 etcd
 ```
 
 Or you can set the values within the configuration file:
 
 ```toml
 [peer]
-heartbeat_timeout = 100
+heartbeat_interval = 100
 election_timeout = 100
 ```
 

+ 4 - 2
README.md

@@ -2,8 +2,6 @@
 
 README version 0.3.0
 
-[![Build Status](https://drone.io/github.com/coreos/etcd/status.png)](https://drone.io/github.com/coreos/etcd/latest)
-
 A highly-available key value store for shared configuration and service discovery.
 etcd is inspired by zookeeper and doozer, with a focus on:
 
@@ -47,6 +45,10 @@ _NOTE_: you need go 1.2+. Please check your installation with
 go version
 ```
 
+See the [development tools documentation][development-tools.md] for alternative build methods like using Vagrant.
+
+[development-tools.md]: https://github.com/coreos/etcd/blob/master/Documentation/development-tools.md
+
 ### Running
 
 First start a single machine cluster of etcd:

+ 27 - 0
Vagrantfile

@@ -0,0 +1,27 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+#
+Vagrant.require_version '>= 1.5.0'
+Vagrant.configure("2") do |config|
+  config.vm.box = "precise64"
+  config.vm.box_url = "http://files.vagrantup.com/precise64.box"
+
+  config.vm.network :forwarded_port, host: 4001, guest: 4001
+  config.vm.network :forwarded_port, host: 7001, guest: 7001
+
+  # Fix docker not being able to resolve private registry in VirtualBox
+  config.vm.provider :virtualbox do |vb, override|
+    vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
+    vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
+  end
+
+  config.vm.provision "docker" do |d|
+    d.build_image "/vagrant", args: '-t etcd'
+    d.run "etcd", args: "-p 4001:4001 -p 7001:7001", demonize: true
+  end
+
+  # plugin conflict
+  if Vagrant.has_plugin?("vagrant-vbguest")
+    config.vbguest.auto_update = false
+  end
+end

+ 39 - 37
config/config.go

@@ -24,24 +24,25 @@ const DefaultSystemConfigPath = "/etc/etcd/etcd.conf"
 
 // A lookup of deprecated flags to their new flag name.
 var newFlagNameLookup = map[string]string{
-	"C":             "peers",
-	"CF":            "peers-file",
-	"n":             "name",
-	"c":             "addr",
-	"cl":            "bind-addr",
-	"s":             "peer-addr",
-	"sl":            "peer-bind-addr",
-	"d":             "data-dir",
-	"m":             "max-result-buffer",
-	"r":             "max-retry-attempts",
-	"maxsize":       "max-cluster-size",
-	"clientCAFile":  "ca-file",
-	"clientCert":    "cert-file",
-	"clientKey":     "key-file",
-	"serverCAFile":  "peer-ca-file",
-	"serverCert":    "peer-cert-file",
-	"serverKey":     "peer-key-file",
-	"snapshotCount": "snapshot-count",
+	"C":                      "peers",
+	"CF":                     "peers-file",
+	"n":                      "name",
+	"c":                      "addr",
+	"cl":                     "bind-addr",
+	"s":                      "peer-addr",
+	"sl":                     "peer-bind-addr",
+	"d":                      "data-dir",
+	"m":                      "max-result-buffer",
+	"r":                      "max-retry-attempts",
+	"maxsize":                "max-cluster-size",
+	"clientCAFile":           "ca-file",
+	"clientCert":             "cert-file",
+	"clientKey":              "key-file",
+	"serverCAFile":           "peer-ca-file",
+	"serverCert":             "peer-cert-file",
+	"serverKey":              "peer-key-file",
+	"snapshotCount":          "snapshot-count",
+	"peer-heartbeat-timeout": "peer-heartbeat-interval",
 }
 
 // Config represents the server configuration.
@@ -72,13 +73,13 @@ type Config struct {
 	VeryVerbose      bool `toml:"very_verbose" env:"ETCD_VERY_VERBOSE"`
 	VeryVeryVerbose  bool `toml:"very_very_verbose" env:"ETCD_VERY_VERY_VERBOSE"`
 	Peer             struct {
-		Addr             string `toml:"addr" env:"ETCD_PEER_ADDR"`
-		BindAddr         string `toml:"bind_addr" env:"ETCD_PEER_BIND_ADDR"`
-		CAFile           string `toml:"ca_file" env:"ETCD_PEER_CA_FILE"`
-		CertFile         string `toml:"cert_file" env:"ETCD_PEER_CERT_FILE"`
-		KeyFile          string `toml:"key_file" env:"ETCD_PEER_KEY_FILE"`
-		HeartbeatTimeout int    `toml:"heartbeat_timeout" env:"ETCD_PEER_HEARTBEAT_TIMEOUT"`
-		ElectionTimeout  int    `toml:"election_timeout" env:"ETCD_PEER_ELECTION_TIMEOUT"`
+		Addr              string `toml:"addr" env:"ETCD_PEER_ADDR"`
+		BindAddr          string `toml:"bind_addr" env:"ETCD_PEER_BIND_ADDR"`
+		CAFile            string `toml:"ca_file" env:"ETCD_PEER_CA_FILE"`
+		CertFile          string `toml:"cert_file" env:"ETCD_PEER_CERT_FILE"`
+		KeyFile           string `toml:"key_file" env:"ETCD_PEER_KEY_FILE"`
+		HeartbeatInterval int    `toml:"heartbeat_interval" env:"ETCD_PEER_HEARTBEAT_INTERVAL"`
+		ElectionTimeout   int    `toml:"election_timeout" env:"ETCD_PEER_ELECTION_TIMEOUT"`
 	}
 	strTrace     string `toml:"trace" env:"ETCD_TRACE"`
 	GraphiteHost string `toml:"graphite_host" env:"ETCD_GRAPHITE_HOST"`
@@ -95,7 +96,7 @@ func New() *Config {
 	c.Snapshot = true
 	c.SnapshotCount = 10000
 	c.Peer.Addr = "127.0.0.1:7001"
-	c.Peer.HeartbeatTimeout = defaultHeartbeatTimeout
+	c.Peer.HeartbeatInterval = defaultHeartbeatInterval
 	c.Peer.ElectionTimeout = defaultElectionTimeout
 	return c
 }
@@ -245,7 +246,7 @@ func (c *Config) LoadFlags(arguments []string) error {
 	f.IntVar(&c.MaxResultBuffer, "max-result-buffer", c.MaxResultBuffer, "")
 	f.IntVar(&c.MaxRetryAttempts, "max-retry-attempts", c.MaxRetryAttempts, "")
 	f.Float64Var(&c.RetryInterval, "retry-interval", c.RetryInterval, "")
-	f.IntVar(&c.Peer.HeartbeatTimeout, "peer-heartbeat-timeout", c.Peer.HeartbeatTimeout, "")
+	f.IntVar(&c.Peer.HeartbeatInterval, "peer-heartbeat-interval", c.Peer.HeartbeatInterval, "")
 	f.IntVar(&c.Peer.ElectionTimeout, "peer-election-timeout", c.Peer.ElectionTimeout, "")
 
 	f.StringVar(&cors, "cors", "", "")
@@ -279,6 +280,7 @@ func (c *Config) LoadFlags(arguments []string) error {
 	f.IntVar(&c.MaxResultBuffer, "m", c.MaxResultBuffer, "(deprecated)")
 	f.IntVar(&c.MaxRetryAttempts, "r", c.MaxRetryAttempts, "(deprecated)")
 	f.IntVar(&c.SnapshotCount, "snapshotCount", c.SnapshotCount, "(deprecated)")
+	f.IntVar(&c.Peer.HeartbeatInterval, "peer-heartbeat-timeout", c.Peer.HeartbeatInterval, "(deprecated)")
 	// END DEPRECATED FLAGS
 
 	if err := f.Parse(arguments); err != nil {
@@ -384,20 +386,20 @@ func (c *Config) Sanitize() error {
 }
 
 // EtcdTLSInfo retrieves a TLSInfo object for the etcd server
-func (c *Config) EtcdTLSInfo() server.TLSInfo {
-	return server.TLSInfo{
-		CAFile:		c.CAFile,
-		CertFile:	c.CertFile,
-		KeyFile:	c.KeyFile,
+func (c *Config) EtcdTLSInfo() *server.TLSInfo {
+	return &server.TLSInfo{
+		CAFile:   c.CAFile,
+		CertFile: c.CertFile,
+		KeyFile:  c.KeyFile,
 	}
 }
 
 // PeerRaftInfo retrieves a TLSInfo object for the peer server.
-func (c *Config) PeerTLSInfo() server.TLSInfo {
-	return server.TLSInfo{
-		CAFile:		c.Peer.CAFile,
-		CertFile:	c.Peer.CertFile,
-		KeyFile:	c.Peer.KeyFile,
+func (c *Config) PeerTLSInfo() *server.TLSInfo {
+	return &server.TLSInfo{
+		CAFile:   c.Peer.CAFile,
+		CertFile: c.Peer.CertFile,
+		KeyFile:  c.Peer.KeyFile,
 	}
 }
 

+ 1 - 1
config/timeout.go

@@ -5,5 +5,5 @@ const (
 	defaultElectionTimeout = 200
 
 	// The frequency (in ms) by which heartbeats are sent to followers.
-	defaultHeartbeatTimeout = 50
+	defaultHeartbeatInterval = 50
 )

+ 12 - 46
etcd.go

@@ -18,7 +18,6 @@ package main
 
 import (
 	"fmt"
-	"net"
 	"net/http"
 	"os"
 	"path/filepath"
@@ -109,10 +108,10 @@ func main() {
 	serverStats := server.NewRaftServerStats(config.Name)
 
 	// Calculate all of our timeouts
-	heartbeatTimeout := time.Duration(config.Peer.HeartbeatTimeout) * time.Millisecond
+	heartbeatInterval := time.Duration(config.Peer.HeartbeatInterval) * time.Millisecond
 	electionTimeout := time.Duration(config.Peer.ElectionTimeout) * time.Millisecond
-	dialTimeout := (3 * heartbeatTimeout) + electionTimeout
-	responseHeaderTimeout := (3 * heartbeatTimeout) + electionTimeout
+	dialTimeout := (3 * heartbeatInterval) + electionTimeout
+	responseHeaderTimeout := (3 * heartbeatInterval) + electionTimeout
 
 	// Create peer server
 	psConfig := server.PeerServerConfig{
@@ -125,26 +124,8 @@ func main() {
 	}
 	ps := server.NewPeerServer(psConfig, registry, store, &mb, followersStats, serverStats)
 
-	var psListener net.Listener
-	if psConfig.Scheme == "https" {
-		peerServerTLSConfig, err := config.PeerTLSInfo().ServerConfig()
-		if err != nil {
-			log.Fatal("peer server TLS error: ", err)
-		}
-
-		psListener, err = server.NewTLSListener(config.Peer.BindAddr, peerServerTLSConfig)
-		if err != nil {
-			log.Fatal("Failed to create peer listener: ", err)
-		}
-	} else {
-		psListener, err = server.NewListener(config.Peer.BindAddr)
-		if err != nil {
-			log.Fatal("Failed to create peer listener: ", err)
-		}
-	}
-
 	// Create raft transporter and server
-	raftTransporter := server.NewTransporter(followersStats, serverStats, registry, heartbeatTimeout, dialTimeout, responseHeaderTimeout)
+	raftTransporter := server.NewTransporter(followersStats, serverStats, registry, heartbeatInterval, dialTimeout, responseHeaderTimeout)
 	if psConfig.Scheme == "https" {
 		raftClientTLSConfig, err := config.PeerTLSInfo().ClientConfig()
 		if err != nil {
@@ -157,7 +138,7 @@ func main() {
 		log.Fatal(err)
 	}
 	raftServer.SetElectionTimeout(electionTimeout)
-	raftServer.SetHeartbeatInterval(heartbeatTimeout)
+	raftServer.SetHeartbeatInterval(heartbeatInterval)
 	ps.SetRaftServer(raftServer)
 
 	// Create etcd server
@@ -167,34 +148,19 @@ func main() {
 		s.EnableTracing()
 	}
 
-	var sListener net.Listener
-	if config.EtcdTLSInfo().Scheme() == "https" {
-		etcdServerTLSConfig, err := config.EtcdTLSInfo().ServerConfig()
-		if err != nil {
-			log.Fatal("etcd TLS error: ", err)
-		}
-
-		sListener, err = server.NewTLSListener(config.BindAddr, etcdServerTLSConfig)
-		if err != nil {
-			log.Fatal("Failed to create TLS etcd listener: ", err)
-		}
-	} else {
-		sListener, err = server.NewListener(config.BindAddr)
-		if err != nil {
-			log.Fatal("Failed to create etcd listener: ", err)
-		}
-	}
-
 	ps.SetServer(s)
 	ps.Start(config.Snapshot, config.Discovery, config.Peers)
 
 	go func() {
-		log.Infof("peer server [name %s, listen on %s, advertised url %s]", ps.Config.Name, psListener.Addr(), ps.Config.URL)
+		log.Infof("peer server [name %s, listen on %s, advertised url %s]", ps.Config.Name, config.Peer.BindAddr, ps.Config.URL)
+		l := server.NewListener(psConfig.Scheme, config.Peer.BindAddr, config.PeerTLSInfo())
+
 		sHTTP := &ehttp.CORSHandler{ps.HTTPHandler(), corsInfo}
-		log.Fatal(http.Serve(psListener, sHTTP))
+		log.Fatal(http.Serve(l, sHTTP))
 	}()
 
-	log.Infof("etcd server [name %s, listen on %s, advertised url %s]", s.Name, sListener.Addr(), s.URL())
+	log.Infof("etcd server [name %s, listen on %s, advertised url %s]", s.Name, config.BindAddr, s.URL())
+	l := server.NewListener(config.EtcdTLSInfo().Scheme(), config.BindAddr, config.EtcdTLSInfo())
 	sHTTP := &ehttp.CORSHandler{s.HTTPHandler(), corsInfo}
-	log.Fatal(http.Serve(sListener, sHTTP))
+	log.Fatal(http.Serve(l, sHTTP))
 }

+ 7 - 15
fixtures/ca/README

@@ -1,21 +1,13 @@
-Testing x509 certs for etcd
+The steps to regenerate this CA:
 
-The passphrases for the keys are `asdf`.
+1. Get etcd-ca source code and generate binary.
 
-# Make the CA cert
-openssl genrsa -des3 -out ca.key 4096
-openssl req -new -x509 -days 365 -key ca.key -out ca.crt  -config openssl.cnf -extensions v3_ca
+$ go get github.com/coreos/etcd-ca
 
-# Make server cert and signing request
-openssl genrsa -des3 -out server.key 4096
-openssl req -new -key server.key -out server.csr  -config openssl.cnf
+2. Run generate_testing_certs.sh with etcd-ca binary location.
 
-# Sign the server csr and generate a crt
-openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt -extfile openssl.cnf -extensions v3_req
+$ ./generate_testing_certs.sh
 
-# Output unencrypted server key
-openssl rsa -in server.key -out server.key.insecure
-
-# Output "raw" public key from server crt
-openssl x509 -pubkey -noout -in server.crt > server.pub
+Details about generation are in the generate_testing_certs.sh script.
 
+3. Check current directory to see regenerated files.

+ 0 - 21
fixtures/ca/broken/README

@@ -1,21 +0,0 @@
-## Testing x509 certs for luvit
-
-# Make the CA cert
-openssl genrsa -out ca.key 4096
-openssl req -new -x509 -days 365 -key ca.key -out ca.crt
-
-# Make server cert and signing request
-openssl genrsa -out server.key 4096
-openssl req -new -key server.key -out server.csr
-
-# Sign the server csr and generate a crt
-openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
-
-# Output unencrypted server key
-openssl rsa -in server.key -out server.key.insecure
-
-# Output "raw" public key from server crt
-openssl x509 -pubkey -noout -in server.crt > server.pub
-
-# Sign the public key with the key (just for testing signatures)
-openssl dgst -sign server.key.insecure -sha256  server.pub > server.pub.sig

+ 0 - 33
fixtures/ca/broken/ca.crt

@@ -1,33 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIFtTCCA52gAwIBAgIJANfWYo0ePBBqMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
-BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
-aWRnaXRzIFB0eSBMdGQwHhcNMTIwMzE1MjMxMzMwWhcNMTMwMzE1MjMxMzMwWjBF
-MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
-ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
-CgKCAgEAyzBV/DH0PWmPxdnr3BTogxPYKVJQ4OQjiiALRRtYPjvQYKPWWsbt4Zlw
-kfYsc6ihIKUL1tGEnjD6YDTtTwWH7DXeq0mWebFr3kQg/ssoTM5oHN9VglwPMRnx
-7qqbBG0/LO/K2Go/UMFGmWHiRYRWcOYegq6DXJpj1sRJz8o3uk4Fxz/xr1sjng1l
-EfAfE4segFLRhmXy1e6Ooy2U5WcpDeKGrD1O01DKsYdR+RavcgkmFYfZ5rdtaKrE
-wpYLylJNmOAkss7w5tOyEEDLoZHtkRFX5Ss38wuU2h9Li8P9vhyL4Ylzcuy/pBXW
-MA89D8bBXjR3G4Hk7qX7gqlI9GdRXtPqnRpgEy/vw/+6aJVfNJtLIRdabSr3vStL
-rhF1y4ocr8OJdNjHGp8tssc9I0LhhItT7bWgjQLHTRezVXV5kzpggAlDCQc48bdc
-aYjBoLuu8jH9mgGCnPtrJMyV+T96rV5V4XJieA9k4IQ3nWJk1Nslqm5S/FSQbM32
-+ineL0ZlT/x8qXNnL3FHQFDOKCng3Ww6wC7M9BDf9+Di04lNtd37pri/i5dcvsn1
-WaYzvHpSGom234Bl4NQSoupKlEhfgTc5w/uuhbGSWcsH+wB4Yi7dg5U7voNkwtVo
-loEaZ58ldd4Dkz16lZSSg4wzwDbMQWpGCPRAfVDAVk/AxpfOiOsCAwEAAaOBpzCB
-pDAdBgNVHQ4EFgQUpahFGYUWGD8RygULRRlo6TlAkxowdQYDVR0jBG4wbIAUpahF
-GYUWGD8RygULRRlo6TlAkxqhSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT
-b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDX
-1mKNHjwQajAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQAvdXT3GIjK
-diHYGurJKSGhja8nVcZ9vm0SICyMQU3nOaMhNdrFXUP21dqO1xP+uqgc7vHGOyPg
-MNLvamCY7JtmcLEgByG5Z+aeNESdwGjP4Rl4YRB83JUyOHke0cONiJXYTjGwF7FL
-vCXjJm/3t5rTj+gPPMkcN3FtYpiVUn2Ra5LURCiRucsqnStEKiLeIM3WluKOFssZ
-AHGkUEGXpYyuobdBvejCqdc02+ywyqGuV05mOHB7dDAt0eS0tUqaEyoKlWgIuVlN
-770LJGjQkQqa0oYwrbsgKuPjH4zu7MDjzooZsYkEpgPCaK64HQ03mdWYWiqW3KY5
-JxT4TdOwSXQfvmeLbT/By1Qo0m9R2Sqb4Q0t3VDILyJmvTr4dLCRjAMfDaADxiPI
-58cXUeT5kLbF2kHQ8GZIFXpWQRhX5Go0sETlv35HtL9szNK/p2ngob6XkbxJf8rC
-ygP96Xa09J94CPrJF34slRM3hsdf/t92ytG8HTOf+42QjT60zgApibVVXwEYwx2S
-M/1FZbt9xR2nfvrKBZG4luyPuIVbAI3VbtgfP2ywIxQI7OkBQec52Ck2e4AvZk9q
-PUgxRqZbzpQSdEr3U3bhEtKf/Yq3Lgx/4Luo11BlZkWRKViBpK1yTUe1C4UkFo5Z
-gZO0oCwwO5YWxTCA1xCJDJeSuz16snOXpw==
------END CERTIFICATE-----

+ 0 - 51
fixtures/ca/broken/ca.key

@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----

+ 0 - 29
fixtures/ca/broken/server.crt

@@ -1,29 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIE/jCCAuYCAQEwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQVUxEzARBgNV
-BAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
-ZDAeFw0xMjAzMTUyMzEzNDZaFw0xMzAzMTUyMzEzNDZaMEUxCzAJBgNVBAYTAkFV
-MRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz
-IFB0eSBMdGQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDOwQ+StykH
-YPBXIQPgD+ZqU4RBgbwpT94p9Fvf5/OZIenI2Ujv9wCEeRbwdtbKj+RlvcM8BX2I
-27MNp0dd5G9+dLizKbDgigjDRJXVNk4d9RPd7r/51jBT3WIkkI+4qkbTMjqfhkJn
-iBQZisAajucaO02F6FGxdqlFcU/l92CG12MRbvmNqmJ9odWniylKHwAfV1jSbnQS
-ckvtsxAc9bCNm3mDrjAwZlZCaHWEy0sq3VoBA7y1IWxOSg7aXR5t6NHEMs61ARfQ
-/bdGR4ww4haGVdflS68oSZtOa9TJytv0BaMnIFLkFDvEpq39KXjP0yrSGqJQC5cA
-3yh3uJQxUcZ9A2rPRR7LoS6ZVEk+4OWRP0kPW6FILDRiHMOlj0jeMmfLzLGLW/NW
-WBPBAG6pwNJaO1Y4LLHZRUJE3dM70v/lNMmB/T1S3kJ7TX+pluTgdCKo2qjxvnQI
-A0CnQeeQSV7TiefuIixXwAGGdg7dZO0MSkX8NASPiS/B3KWP4pSDDEzsRmz4UqRG
-0tnkFRfCKHtkBPJ7OWdHapgXnlUBImQWO2MStPenmZBzuVcCkv52QwuFC8g/EKlr
-tvzRl11Ajgg9LZApI0BaOtXE/LGkBpPpXNh0Pi10ETniPhvfZhX/RYb4g6WeVGsx
-oD6V9vHrD1Psp4u+QKZRldsD0d1aRvXvzwIDAQABMA0GCSqGSIb3DQEBBQUAA4IC
-AQCAzJrMHAIZVPupdJiiooCHvLc3M/4wn02Wws/NgvkIO3mNs+9uZvJ/IsLSOS/0
-x9gIVIXscoT0y/RRCg9IUwCGmCp9XkfL0MzBNPfhOXZ2/SXLGv2ubBTv7nyXAeF9
-Oh719bbir+vmEKoMXej0LBQ3qGT6zS8Zs2iKGj1bXZjZXiTt67YkYZgr65uZTYW4
-XtywTnJ+vUg9Mp6fReXgOWDlM8BiJ6JKnRn9f5Y66INSePV4NvtcIrqNNvrBEDqX
-LOWuh1Vs32gOySF8A1jM/GdSCdV1Wsng5HxGMMuGAKnw35YguW598Fk8LLfE8w5V
-x9Gth2RdxvimMu+qsNMq0mc78C1yPDSfRXC51t8J8d5+hke/apb6KfB/47gooQeH
-TCRMorOzO8tWhK6NDPp9iKoNSYznmtWq+0Lc4Upa+cc3ktIOCiTWh9OBaFsFd8jB
-Dlhw3sqwhMtqxJEoEJIZMGSE0W9p9y+D1XeNqfHmJ04NaTvuqfkt2z6ROd+pPdqb
-A+b6aFZfBdh+ynOq2g6Epwq8rNe338E23gVGgNfcw4pdFq9NmpdVKREIQKObQWCQ
-oElaQwIgyPI9rkpkT3QsHHJnEb9mRn05tlEplOi6S05/NIb+yz07Jb09UdAjxHDR
-4MiUfXVXZwUAvuWKBnKK4ZjjgEZe21aoliLDl3yekewVqA==
------END CERTIFICATE-----

+ 0 - 27
fixtures/ca/broken/server.csr

@@ -1,27 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIIEijCCAnICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
-ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCAiIwDQYJKoZIhvcN
-AQEBBQADggIPADCCAgoCggIBAM7BD5K3KQdg8FchA+AP5mpThEGBvClP3in0W9/n
-85kh6cjZSO/3AIR5FvB21sqP5GW9wzwFfYjbsw2nR13kb350uLMpsOCKCMNEldU2
-Th31E93uv/nWMFPdYiSQj7iqRtMyOp+GQmeIFBmKwBqO5xo7TYXoUbF2qUVxT+X3
-YIbXYxFu+Y2qYn2h1aeLKUofAB9XWNJudBJyS+2zEBz1sI2beYOuMDBmVkJodYTL
-SyrdWgEDvLUhbE5KDtpdHm3o0cQyzrUBF9D9t0ZHjDDiFoZV1+VLryhJm05r1MnK
-2/QFoycgUuQUO8Smrf0peM/TKtIaolALlwDfKHe4lDFRxn0Das9FHsuhLplUST7g
-5ZE/SQ9boUgsNGIcw6WPSN4yZ8vMsYtb81ZYE8EAbqnA0lo7VjgssdlFQkTd0zvS
-/+U0yYH9PVLeQntNf6mW5OB0IqjaqPG+dAgDQKdB55BJXtOJ5+4iLFfAAYZ2Dt1k
-7QxKRfw0BI+JL8HcpY/ilIMMTOxGbPhSpEbS2eQVF8Ioe2QE8ns5Z0dqmBeeVQEi
-ZBY7YxK096eZkHO5VwKS/nZDC4ULyD8QqWu2/NGXXUCOCD0tkCkjQFo61cT8saQG
-k+lc2HQ+LXQROeI+G99mFf9FhviDpZ5UazGgPpX28esPU+yni75AplGV2wPR3VpG
-9e/PAgMBAAGgADANBgkqhkiG9w0BAQUFAAOCAgEAyqppt9mxmlqAUkYCbNxPnc+M
-1d5OQZ1Fqy0a4eF9/WxK+PqjRTKbD+rvGEulYdeCGiz8wP5HxVCdT2xzdVZdhMUX
-opZGG3x5H1xXy0YLzBsxB9rkYjz+NeVtl8lKXvWDfgZ1vjjRHOIc261Eq6CPoXjT
-5ENHnTyT0xbDmdkyjGNT0qowl50rlZotx6Vb2VPquAtau1m2nrvx5t0wkbJPocPA
-XTndphgdH0aecJXZOgN8MWh9LYObNM5UqIFPaiNHHAetJIOLoDDIpEl5ZVj4PwtU
-uiiaWpNjz3ODx2j5tmEz1SUF+6vS0OfvKx/pInQzFFRLfudgphzGYLf9rwOswBI7
-8d0sEfrUNEladzvIz/IvJpuRrWJ/uLfpE4LXYTNbGWP50d1YRGxv7Zl8Bio0CU34
-q+Du1CXpWce5bcOJ25KYZd7Lrf0YVzQjneuyNbBCPrp2gbweeydQWd6LGdtUab0l
-gjQ3lj4E8Y1vIpTOL2K3bvkqJxJYoaYdzzGEzuv6/FS7ATYVn5sBYxJrsUqgYdjp
-SMx6RS6ImNbHVy56nb6MiaztwAE4uo59vkrdKdvIETvP5duD4qDBsZL3WzJwhMxl
-d9An+z3VAqEABzNtM7/Cdq7pZmgdPAHgGFasB3eihdmHsONWqExRPhcmW4H1hpVQ
-pkguJFDOpRqebdLHZPI=
------END CERTIFICATE REQUEST-----

+ 0 - 51
fixtures/ca/broken/server.key

@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----

+ 0 - 51
fixtures/ca/broken/server.key.insecure

@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----

+ 0 - 14
fixtures/ca/broken/server.pub

@@ -1,14 +0,0 @@
------BEGIN PUBLIC KEY-----
-MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzsEPkrcpB2DwVyED4A/m
-alOEQYG8KU/eKfRb3+fzmSHpyNlI7/cAhHkW8HbWyo/kZb3DPAV9iNuzDadHXeRv
-fnS4symw4IoIw0SV1TZOHfUT3e6/+dYwU91iJJCPuKpG0zI6n4ZCZ4gUGYrAGo7n
-GjtNhehRsXapRXFP5fdghtdjEW75japifaHVp4spSh8AH1dY0m50EnJL7bMQHPWw
-jZt5g64wMGZWQmh1hMtLKt1aAQO8tSFsTkoO2l0ebejRxDLOtQEX0P23RkeMMOIW
-hlXX5UuvKEmbTmvUycrb9AWjJyBS5BQ7xKat/Sl4z9Mq0hqiUAuXAN8od7iUMVHG
-fQNqz0Uey6EumVRJPuDlkT9JD1uhSCw0YhzDpY9I3jJny8yxi1vzVlgTwQBuqcDS
-WjtWOCyx2UVCRN3TO9L/5TTJgf09Ut5Ce01/qZbk4HQiqNqo8b50CANAp0HnkEle
-04nn7iIsV8ABhnYO3WTtDEpF/DQEj4kvwdylj+KUgwxM7EZs+FKkRtLZ5BUXwih7
-ZATyezlnR2qYF55VASJkFjtjErT3p5mQc7lXApL+dkMLhQvIPxCpa7b80ZddQI4I
-PS2QKSNAWjrVxPyxpAaT6VzYdD4tdBE54j4b32YV/0WG+IOlnlRrMaA+lfbx6w9T
-7KeLvkCmUZXbA9HdWkb1788CAwEAAQ==
------END PUBLIC KEY-----

BIN
fixtures/ca/broken/server.pub.sig


+ 30 - 0
fixtures/ca/broken_ca.crt

@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFNDCCAx6gAwIBAgIBATALBgkqhkiG9w0BAQUwLTEMMAoGA1UEBhMDVVNBMRAw
+DgYDVQQKEwdldGNkLWNhMQswCQYDVQQLEwJDQTAeFw0xNDAzMTMwMjA4NTRaFw0y
+NDAzMTMwMjA4NTRaMC0xDDAKBgNVBAYTA1VTQTEQMA4GA1UEChMHZXRjZC1jYTEL
+MAkGA1UECxMCQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGyDN5
+EcwViDB+EpOvZNfW9AJ79EiY8yiGEgXF9M3BLVFoB09wDT0ASEmPrpZhMSZb1eSM
+emUFELzuEEDtuEG0TrFFNI3/gScZm8Mp6+d7oCqWRF87ecBLfAUCmr3vA0VD/KQz
+dj8F+4UWwEXkeyzJ67vPW5n35CJrZOq9Yp8bPUoVvE1/L03wbQHeIXynEHmgPIB2
+syVuQwluhrgpNX8kEsWpy3GzusuorJ3jcTONin4y4h9pgaAZvR32mhXmCRh/6i+y
+u4NOsx11kSAgPBWdLJBQcj5HH3YZan/7n+ygTAIbXKI9sHxgPYPgXW0P/7OhaUiH
+LEcDw2ipGpYxZxj/8hkQVJ1b4YMgD4TIFHix9TeASufsZUwEkuiTYncXp7q38feq
+4z4UNqgn5ozgOT0kqVDGSiEOJTH81Tq1SUhC7FsN6UkgIVWGSYsPMyxc7V+tH2EV
+qpWCnRuw9MPr+CEZfGzM88alc65ZOwmxgIU4heTTi457d5RgXqinCBCfcUdHTFYa
+E65MWh0FdO1jrSw7KzFDd+vt0ajZ1K64+tbBhV4QvIrd2ktQDMk3InawFx2GAIEv
+l9TxC8jf+RoamPxt7sPwPzvZuiSXj4mO1H3GdiSBn94ZVzOnDT+UV/TwwyphvCtB
+OOzV2SlKZ/bl1CmD7JoRpw18W0J72RV0vd219QIDAQABo2MwYTAOBgNVHQ8BAf8E
+BAMCAAQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUze1XmPTcVbM2gSnXwfVx
+1VQ/ZYwwHwYDVR0jBBgwFoAUze1XmPTcVbM2gSnXwfVx1VQ/ZYwwCwYJKoZIhvcN
+AQEFA4ICAQC/4a4QiGt3NhEhl2fWMYDF+EAtQj82bFT8fod8rsQQZddI0baNOzTa
+9GRzAqChpoDaxzH/ErhlMs6nLL7NC6NB8Jr596cgakbIOpLcQujoajDAvF7HhbGE
+cnZWXFdjEZNkAg5RM3jCcjlnZ6HfsMlQJGO3mGPGMjHIhKv+RLquXY7NOy3fczrI
+THyJFfL99l77u3Ieo6MstfWog5IUM0PmNzf6ocXJ4o5woQcrSLJVx7hlHaU7iTsV
+e//oHh+S/yJHmeOzmI3LayMOEGRGjP2SJBqTuOak4cINutoqJnRnt8JIOvur9zCG
+rEm6aCTRi/5fqMys4vQuATPalI9/lLQa9NlMs1xoFAGjE6j8ybAUWxrAWdTWOOQd
+S7IA0TU2JTIcWt8fbKUwf4gkO8rfCcj2Y2Y4xZQu8uqZI7uXCse8yNE2zWPTqCPf
+BzA9kZs42AP47mknVGSl59HNAovBTjQuXlFZZ1BnBDAzCq49sDR5wcr4uetC1rss
+dFfFqQPPonAGcEAaKsSoaOHgVaBHfufMM7nq8JqJvZGOmLFhUgsTO3xEgnpDts9h
+cfhSS/5YVofp2Vy3+++yIkSC3NDuyNCPwfzSLA2pW1GoiIv0hdSco7ALuG9FxsCs
+gSc5qcrrTkazbjlCwM9rgM0BEH8pXOAkRAEZLlKd9eg6Lh3q4yBnhw==
+-----END CERTIFICATE-----

+ 54 - 0
fixtures/ca/broken_ca.key

@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,29382520d522b331
+
+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
+-----END RSA PRIVATE KEY-----

+ 31 - 0
fixtures/ca/broken_server.crt

@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFYjCCA0ygAwIBAgIBAjALBgkqhkiG9w0BAQUwLTEMMAoGA1UEBhMDVVNBMRAw
+DgYDVQQKEwdldGNkLWNhMQswCQYDVQQLEwJDQTAeFw0xNDAzMTMwMjA5MDdaFw0y
+NDAzMTMwMjA5MDdaMEwxDDAKBgNVBAYTA1VTQTEQMA4GA1UEChMHZXRjZC1jYTEW
+MBQGA1UECwwNYnJva2VuX3NlcnZlcjESMBAGA1UEAxMJMTI3LjAuMC4xMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAymbo/4oo+oi0poLAvNyXIyFLw3xC
+uO0pcALKCnA84KzTopAnB45+HLKtovkIyi4j2aSEWGVw1xXE6hsJJxpcuDeBGhA3
+4QdUP2OyYWs65rSpx1G8Yxm5zBd8wGO0dRgSA2dAUgWETUXmaRuiR7W2NLDzZna0
+LEe8jATn9Ex3o5IeVUAx3fw/sDr1AD/HkDh2dhoXB9PVDGqGBizS0qzRsDJxhH7f
+xwL1Wb4Ug7YQ0oaWniZ8dPDlxbXC4C0xsaOb6rQGSS91ivZdvNksjht4qrFnCHYO
+olqI4IFet9P/B7hcaPTe0oExoTZd0o3XNrZR/rfG0MPuKA5U2BG1E+VnntYamp/l
+fds1YYahvFUoJVQ5PzwY38nb2JjQm14iM2jzWrLrqKjkdEdqH9Gf7j8vlXki9Riq
+4vO665z0zim/u2CpraQq2VhgX6ShmoV4vmgEnD3p4MwbXqHkKtU/1kMSo15HSKBe
+b3GSFPI0EDpFYfUp0FyfFGfHtgw/Y7V8z6g65Z5c3xjvIb0r5/40mRTUJpoCJbdG
+5RG6pxBUsnAVM01G6TH5TPkRgWo+0it6+qQOAObNoPqN0dyGAfN8T1zD+BEMe6H4
+6toKSyGlAYyCcnSspy8JA9wEm91ZkLqxkc5qeav6ZPqCcN+Clf544XSWEHVLcgPD
+VpYNXNT2DZRtJZUCAwEAAaNyMHAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
+BwMCMB0GA1UdDgQWBBQX2fL+Aa48uIwgyYFJOoKGfIqRSzAfBgNVHSMEGDAWgBTN
+7VeY9NxVszaBKdfB9XHVVD9ljDAPBgNVHREECDAGhwR/AAABMAsGCSqGSIb3DQEB
+BQOCAgEAfYKCbV/hvhSUqvNmQBtl8GkZ2WrDvduRQ+nQLXiUpgO28k1bieKA5SrF
+/yUyjUEoXP2lQdYxGiKNzXP+WCkldPn4mrbaAbktJABuqFW+AotOyu2juyNR8nTx
+xBFULKpXgF+DDXa7DnSqjaxEWz8TFLWTb8h1pc7Ha3kejB1U3V6HbcxLBLVLL4vJ
+ETU0V6PnjYOmHZJCQLixB4h4v0mCCns1u3OXQaR1dL9PwRVL7qzpFTbp0jF9Y2LO
+gJFuW8if80le5A+ovXJLJMoAKpKeLfSRRzPy8j4EgLw/iJO5ZLJMWOIEa1P/SLap
+xfL8yfRg5jBaMhHtdhbIku1Kmyqypch1XoZ8w5hNFY/pDVL0tff+cslzUJrFLMa/
+XKZqHnX07dN5Cyz/oAZSz4DhZ2LlYrNq0FTo8tqNr3tf7oakpyFr6fr6i8u7Mmwx
+U6Dsg1yJXnb0nA5ROWC8td714S7oIvzFbn69pyEnwGmCxBHvfTIBKoWeYlUTSxJu
+7e3wR7yia5GV8ZgmzKgB9FUd0VBDYOR3quFl0ixxv2n2slqlRwbfbJTwBdijoaCs
+FlYb3WlRRFQ1s2MiWwVHQY/SMXtan5xwT0l99pWVMS/D0y6tjALHKXWfDT4Pp/wX
+xNjPqNR6L4Ru1eTb6nuUGzKAgonn/w9bYXqLiYnRye9pIf00gtU=
+-----END CERTIFICATE-----

+ 51 - 0
fixtures/ca/broken_server.key.insecure

@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----

+ 28 - 29
fixtures/ca/ca.crt

@@ -1,31 +1,30 @@
 -----BEGIN CERTIFICATE-----
-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
+MIIFNDCCAx6gAwIBAgIBATALBgkqhkiG9w0BAQUwLTEMMAoGA1UEBhMDVVNBMRAw
+DgYDVQQKEwdldGNkLWNhMQswCQYDVQQLEwJDQTAeFw0xNDAzMTMwMjA5MDlaFw0y
+NDAzMTMwMjA5MDlaMC0xDDAKBgNVBAYTA1VTQTEQMA4GA1UEChMHZXRjZC1jYTEL
+MAkGA1UECxMCQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDdlBlw
+Jiakc4C1UpMUvQ+2fttyBMfMLivQgj51atpKd8qIBvpZwz1wtpzdRG0hSYMF0IUk
+MfBqyg+T5tt2Lfs3Gx3cYKS7G0HTfmABC7GdG8gNvEVNl/efxqvhis7p7hur765e
+J+N2GR4oOOP5Wa8O5flv10cp3ZJLhAguc2CONLzfh/iAYAItFgktGHXJ/AnUhhaj
+KWdKlK9Cv71YsRPOiB1hCV+LKfNSqrXPMvQ4sarz3yECIBhpV/KfskJoDyeNMaJd
+gabX/S7gUCd2FvuOpGWdSIsDwyJf0tnYmQX5XIQwBZJib/IFMmmoVNYc1bFtYvRH
+j0g0Ax4tHeXU/0mglqEcaTuMejnx8jlxZAM8Z94wHLfKbtaP0zFwMXkaM4nmfZqh
+vLZwowDGMv9M0VRFEhLGYIc3xQ8G2u8cFAGw1UqTxKhwAdRmrcFaQ38sk4kziy0u
+AkpGavS7PKcFjjB/fdDFO/kwGQOthX/oTn9nP3BT+IK2h1A6ATMPI4lVnhb5/KBt
+9M/fGgbiU+I9QT0Ilz/LlrcCuzyRXREvIZvoUL77Id+JT3qQxqPn/XMKLN4WEFII
+112MFGqCD85JZzNoC4RkZd8kFlR4YJWsS4WqJlWprESr5cCDuLviK+31cnIRF4fJ
+mz0gPsVgY7GFEan3JJnL8oRUVzdTPKfPt0atsQIDAQABo2MwYTAOBgNVHQ8BAf8E
+BAMCAAQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUnVlVvktY+zlLpG43nTpG
+AWmUkrYwHwYDVR0jBBgwFoAUnVlVvktY+zlLpG43nTpGAWmUkrYwCwYJKoZIhvcN
+AQEFA4ICAQAqIcPFux3V4h1N0aGM4fCS/iT50TzDnRb5hwILKbmyA6LFnH4YF7PZ
+aA0utDNo1XSRDMpR38HWk0weh5Sfx6f2danaKZHAsea8oVEtdrz16ZMOvoh0CPIM
+/hn0CGQOoXDADDNFASuExhhpoyYkDqTVTCQ/zbhZg1mjBljJ+BBzlSgeoE4rUDpn
+nuDcmD9LtjpsVQL+J662rd51xV4Z6a7aZLvN9GfO8tYkfCGCD9+fGh1Cpz0IL7qw
+VRie+p/XpjoHemswnRhYJ4wn10a1UkVSR++wld6Gvjb9ikyr9xVyU5yrRM55pP2J
+VguhzjhTIDE1eDfIMMxv3Qj8+BdVQwtKFD+zQYQcbcjsvjTErlS7oCbM2DVlPnRT
+QaCM0q0yorfzc4hmml5P95ngz2xlohavgNMhsYIlcWyq3NVbm7mIXz2pjqa16Iit
+vL7WX6OVupv/EOMRx5cVcLqqEaYJmAlNd/CCD8ihDQCwoJ6DJhczPRexrVp+iZHK
+SnIUONdXb/g8ungXUGL1jGNQrWuq49clpI5sLWNjMDMFAQo0qu5bLkOIMlK/evCt
+gctOjXDvGXCk5h6Adf14q9zDGFdLoxw0/aciUSn9IekdzYPmkYUTifuzkVRsPKzS
+nmI4dQvz0rHIh4FBUKWWrJhRWhrv9ty/YFuJXVUHeAwr5nz6RFZ4wQ==
 -----END CERTIFICATE-----

+ 50 - 50
fixtures/ca/ca.key

@@ -1,54 +1,54 @@
 -----BEGIN RSA PRIVATE KEY-----
 Proc-Type: 4,ENCRYPTED
-DEK-Info: DES-EDE3-CBC,5AD5C95753A0AE70
+DEK-Info: DES-EDE3-CBC,d2e70acc12a86116
 
-NW4oxy0HmyCZQluW6/g8A+LxWALNpscFiFWSVIVaqfOWD1sFZxzsP25pG6/dE3cR
-sg5PlVZ2UOlTHoaqmtA21UKDuXnwmczqMrWtLegyz5WQLw2DpfMiEbaMhcwIy60m
-yKh9N9Agf2gE+SumpzG0De338zJhcc28ogStr6d4VECbCHqdsj39cF4Os44FH/ns
-PT332vLt1umLyYgMhvZWaXZGlYARU51vsB03Jb5vNyZPOfPbEWT6+jIukFWYuBK1
-WeccnYhjtFWjCg9zhOyYLyjb2kPJORvIPBwkjkjhRnFnMyJpRVEZNSseE1dDyzZV
-dGgVn4HmydJY4L+Mcaexp2CLAPxGEmv/C6/2sz5sffQYZ3+3BGMjXOUJSTzpaKNj
-wSLfZFiY5nZ2TpLucRmlUHU+kJvPmJzPzSPl3QrLVC/Gb0Wy3HbEZl4KWvaoTdIL
-aoBFx32j9KvnvfU9x+i2wj1Z+bFig/yH4Sxp+2EpUyE/9Hqg7se14Eg1F04skb+K
-Uorz9JELyQ01Mm6gxYhVzL1UPJKfEq7NBwDyFnjSqB2zUFnNnsizYkSgT/wBRo/9
-BtLI0YjXqeq2Y5OEvZ1t6a1XB9gLOx9isyA9GooXR6iaWg+9vwOm2Woo5MgNWA4b
-JUYteTqXktdkqMgk7Rzz/xB9U/bAo67gMz3LR9SYYX6YPOEMcTLQfF1njPzD0fCk
-WhWTfVOmPNJFoZUe5pfA5uLpNmC8fIDCFYFwRInImKfSd7SBQ70cF9LV8xvjOHCt
-Sl2cdipumILO5+UfsN5EAhMPpeh8Xk+b54IAE77tnE+CRi02nw7G+7Jt/QGIgckR
-ymc2+JYRdbS/Wd87nY7ipTKspV/1Bjus9cokKD3mWfURPzQOWWNpgRsB+dnkK+HB
-muecoNeoWwLQ6Z11wU5B36qVtFj1/wfkMUH8LlBXqfIDHmvH9vNeLOykiZyudn0n
-CjN6+hq9Gy49T141lw3Ek9XHo4mly6lQQtFdtNI9/PHyGlctqVIeMZ4WXvE3ohxq
-mq0AMWqb39BHXjrno3gru039rhe6Jf/+vhbpsF+ZT1LquptL9x+ggV1U4D7d2/Mw
-oY3DKc5jjUYsnJNFWarSc8M0HNaHcYXi6HshfYDqamUVWiQVaw2W4lLti8A+vBuw
-NwWHQ0vI8XwIKEiWJ9/e7xSNuHp7SF8zbBKDXgnuzG6V76iASay2xEtpoJUTRMk7
-ckMa+x4IftAr2Z5KuJk9As6mSDIraTkWu7lnkIt9C3dXJR5yOIhF+XkQOoKSEjJ2
-lKh7rkz8O5HkvAubXre64MYvokdxhCFQaHPGcB810+hJQBgAcClWLHoF9acY/3ca
-L6tnoGqS+B3ZPugtQqHkLzFLMMB/qI7MUUNp7DcA6P8H35L0fpX3I6mg4cty0G75
-f96KnUQDuHxis9Vqf46dh2vjZXYHAOnjTl/BqxY+vqmzSRblY81Sx+2q1U5u4OvG
-WuotIykrAMIxDED66fE+67+kugrX1AMVNCty82BA+xeXim/cExOxnn578hhtIWiI
-aBl1zq9KhACnHC7tVNrHXA1auhIdpk0i+WCMnPI5KFfTcSL3/PZaf/3+IagRouNl
-pLK6Y3ZGgEbxL+fD7p6PJ4UQUUFV5bKHEo4S6sb0iqYBRZwfQq4VCU1bdtETMc9S
-7/0ViTdOiEb37/Wblyqs1Q/aIZ809nPobGxkCY4uYnLZIYdvlxAO7eLS5cLZ3Yln
-JFR/be0wQ4hztLgcqyVpr53qiVrlZkuRgwdyBiOp9Lf051M7hsNlEuVy32LFV4or
-ED5fskmKhTpsX7gxhXWPqZbKYJo27Ahze2JA/luq5Abav5wq4eXduf4I0SDUlwEV
-BLZVq0uIz/lHc1JrfuD+d8mUV8NMUByrKATX2kEccfYgepAs8IcBdXJfYTBmDG+7
-JU2cBbMCV1Ktf0tL8I/njI6QEwcPVjI+2fzKEkV6qz37Gwb6Fe5MT+1KtecuxG4a
-vYEKVXYlSEJSxeXDV/Mdb/iXn1CoGG/A/EXirq8GLZUo3J8ftwh2EFkKLjJ/jLdr
-RvtJqBg/6AD23DHwD33znpG9wKj8XY5vPJ4+K7xDvURhyqZWt7e6BkeKDaMwFAH6
-Hk2WEy48c7X1PRspOrHpRGZdVOjlElDQWrWr8eDZNl9E6noXuXDLampNp0sURvi/
-py22lh0xAGPxjj+N3ahNqweVt4pRmNmprSIkw7eX4jbe5XTdzp4YNGvA4zUNZqB5
-8WElwbVZG+F3kx8+vJ10z/PSIP1GaF9wk6ChqBNi945A8VBkZkry1W5Km63AQCUW
-ejRjYm9TnV2zhIS9eVL80+LSJhyTobZbebQ/1gVck0SIpbm7uyp0mGNxvhtV25SA
-t/WIFyOBWQzEDyzmc27VEHtr9mIZi25AiGaktzMfLKEZ+ZSfMKo3udwHc3uKX9fg
-iVN/arnKFbtRSYI7DMJsFYLFyLASy23jNn+OhxgNJRGvhmaaHpxLoL0kCBqIIqha
-SffOlLhCPxdqsP6UZ0Y8DJdAGx1LeFzaLbizTrAJ2TmYys2cawRbkH9ufaoBWxpx
-rKYGii6N0yaqk9IJqz/h/WxPJmGuJF8J4yDi7jlJWTmw/l17G74LMFstD8qrUtSN
-Vv4wgEeq6CeqIOCzRpo2E4nJ6uaRRatVyI9sDAb403dgeR76QoM0qhXeVRZgegT7
-iNtI5vhGsSv1xokr1WJ/cyOjnTrcCE7qGH97IMaquIWniMDw6I1vY3eLK6w7gFtD
-HmsA/TQ59PgDVHP1wthzSQFpEXu2fOqrShBDF30m0MV5SlemVHxLYLl9ApEDUWbD
-hQZP9r1zdWzQHiBrO0tot6SFHi4oSCtBY4cquvFAO/9HSdhlBZgBsBl3MFgc1sBG
-E0DqNsHrykoHkSpqSJNNwSz4wxYpGmu1asshv4wBnoG4k4MGDgiuKpQ7BiKfz7EE
-89ZARgv9cac43KZAnP3VvPpLURvjjjarQIuCS5M61vEOYr9e77v+YOQmvmcijWmt
-lNKeTlZMqYYa0FSDxOQ2WtmTqQXCv5oMrgEo5AZ3WWnDus+5UFFlLmgQ9u6u2QB1
-COpJsJ/4+vfqQ+aqWBPIL639kwb2yqJzi3naAqKk/k8ze0BEQC71oFK+nr7s93Oj
+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
 -----END RSA PRIVATE KEY-----

+ 58 - 0
fixtures/ca/generate_testing_certs.sh

@@ -0,0 +1,58 @@
+#!/bin/bash
+#
+# This script is used to generate all cert related files for etcd testing.
+
+# location for temporary depot
+depot=".depot"
+# The passphrases for the keys are `asdf`.
+passphrase="--passphrase asdf"
+
+# etcd-ca could be found at github.com/coreos/etcd-ca
+if [ $# -eq 0 ]; then
+	# try to find it through $GOPATH
+	IFS=':' read -a paths <<< "${GOPATH}"
+	for path in ${paths[@]}; do
+		if [ -f "${path}/bin/etcd-ca" ]; then
+			ca="${path}/bin/etcd-ca --depot-path $depot"
+			break
+		fi
+	done
+	if [ "$ca" == "" ]; then echo "Failed finding etcd-ca binary"; exit 1; fi
+else
+	# treat the first argument as the path to etcd-ca binary
+	ca="$1 --depot-path $depot"
+fi
+
+rm -rf $depot 2>/dev/null
+# create ca, which is assumed to be the broken one
+$ca init $passphrase
+# export out and rename files
+$ca export | tar xvf -
+mv ca.crt broken_ca.crt
+mv ca.key broken_ca.key
+
+# create certificate
+$ca new-cert $passphrase --ip 127.0.0.1 server
+$ca sign $passphrase server
+# export out and rename files
+$ca export --insecure $passphrase server | tar xvf -
+mv server.crt broken_server.crt
+mv server.key.insecure broken_server.key.insecure
+
+rm -rf $depot 2>/dev/null
+# create ca
+$ca init $passphrase
+$ca export | tar xvf -
+
+# create certificate for server
+$ca new-cert $passphrase --ip 127.0.0.1 server
+$ca sign $passphrase server
+$ca export --insecure $passphrase server | tar xvf -
+$ca chain server > server-chain.pem
+
+# create certificate for server2
+$ca new-cert $passphrase --ip 127.0.0.1 server2
+$ca sign $passphrase server2
+$ca export --insecure $passphrase server2 | tar xvf -
+
+rm -rf $depot 2>/dev/null

+ 0 - 337
fixtures/ca/openssl.cnf

@@ -1,337 +0,0 @@
-[ new_oids ]
-
-# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
-# Add a simple OID like this:
-# testoid1=1.2.3.4
-# Or use config file substitution like this:
-# testoid2=${testoid1}.5.6
-
-# Policies used by the TSA examples.
-tsa_policy1 = 1.2.3.4.1
-tsa_policy2 = 1.2.3.4.5.6
-tsa_policy3 = 1.2.3.4.5.7
-
-####################################################################
-[ ca ]
-default_ca	= CA_default		# The default ca section
-
-####################################################################
-[ CA_default ]
-
-dir		= ./demoCA		# Where everything is kept
-certs		= $dir/certs		# Where the issued certs are kept
-crl_dir		= $dir/crl		# Where the issued crl are kept
-database	= $dir/index.txt	# database index file.
-#unique_subject	= no			# Set to 'no' to allow creation of
-					# several ctificates with same subject.
-new_certs_dir	= $dir/newcerts		# default place for new certs.
-
-certificate	= $dir/cacert.pem 	# The CA certificate
-serial		= $dir/serial 		# The current serial number
-crlnumber	= $dir/crlnumber	# the current crl number
-					# must be commented out to leave a V1 CRL
-crl		= $dir/crl.pem 		# The current CRL
-private_key	= $dir/private/cakey.pem# The private key
-RANDFILE	= $dir/private/.rand	# private random number file
-
-x509_extensions	= usr_cert		# The extentions to add to the cert
-
-# Comment out the following two lines for the "traditional"
-# (and highly broken) format.
-name_opt 	= ca_default		# Subject Name options
-cert_opt 	= ca_default		# Certificate field options
-
-# Extension copying option: use with caution.
-# copy_extensions = copy
-
-# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
-# so this is commented out by default to leave a V1 CRL.
-# crlnumber must also be commented out to leave a V1 CRL.
-# crl_extensions	= crl_ext
-
-default_days	= 365			# how long to certify for
-default_crl_days= 30			# how long before next CRL
-default_md	= default		# use public key default MD
-preserve	= no			# keep passed DN ordering
-
-# A few difference way of specifying how similar the request should look
-# For type CA, the listed attributes must be the same, and the optional
-# and supplied fields are just that :-)
-policy		= policy_match
-
-# For the CA policy
-[ policy_match ]
-countryName		= match
-stateOrProvinceName	= match
-organizationName	= match
-organizationalUnitName	= optional
-commonName		= supplied
-emailAddress		= optional
-
-# For the 'anything' policy
-# At this point in time, you must list all acceptable 'object'
-# types.
-[ policy_anything ]
-countryName		= optional
-stateOrProvinceName	= optional
-localityName		= optional
-organizationName	= optional
-organizationalUnitName	= optional
-commonName		= supplied
-emailAddress		= optional
-
-####################################################################
-[ req ]
-default_bits		= 1024
-default_keyfile 	= privkey.pem
-distinguished_name	= req_distinguished_name
-attributes		= req_attributes
-x509_extensions	= v3_ca	# The extentions to add to the self signed cert
-
-# Passwords for private keys if not present they will be prompted for
-# input_password = secret
-# output_password = secret
-
-# This sets a mask for permitted string types. There are several options. 
-# default: PrintableString, T61String, BMPString.
-# pkix	 : PrintableString, BMPString (PKIX recommendation before 2004)
-# utf8only: only UTF8Strings (PKIX recommendation after 2004).
-# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
-# MASK:XXXX a literal mask value.
-# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
-string_mask = utf8only
-
-req_extensions = v3_req # The extensions to add to a certificate request
-
-[ req_distinguished_name ]
-countryName			= Country Name (2 letter code)
-countryName_default		= AU
-countryName_min			= 2
-countryName_max			= 2
-
-stateOrProvinceName		= State or Province Name (full name)
-stateOrProvinceName_default	= Some-State
-
-localityName			= Locality Name (eg, city)
-
-0.organizationName		= Organization Name (eg, company)
-0.organizationName_default	= Internet Widgits Pty Ltd
-
-# we can do this but it is not needed normally :-)
-#1.organizationName		= Second Organization Name (eg, company)
-#1.organizationName_default	= World Wide Web Pty Ltd
-
-organizationalUnitName		= Organizational Unit Name (eg, section)
-#organizationalUnitName_default	=
-
-commonName			= Common Name (e.g. server FQDN or YOUR name)
-commonName_max			= 64
-
-emailAddress			= Email Address
-emailAddress_max		= 64
-
-# SET-ex3			= SET extension number 3
-
-[ req_attributes ]
-challengePassword		= A challenge password
-challengePassword_min		= 4
-challengePassword_max		= 20
-
-unstructuredName		= An optional company name
-
-[ usr_cert ]
-
-# This is required for TSA certificates.
-# This is required for client Auth and server Auth
-extendedKeyUsage = critical,timeStamping,serverAuth,clientAuth
-
-# These extensions are added when 'ca' signs a request.
-
-# This goes against PKIX guidelines but some CAs do it and some software
-# requires this to avoid interpreting an end user certificate as a CA.
-
-basicConstraints=CA:FALSE
-
-# Here are some examples of the usage of nsCertType. If it is omitted
-# the certificate can be used for anything *except* object signing.
-
-# This is OK for an SSL server.
-# nsCertType			= server
-
-# For an object signing certificate this would be used.
-# nsCertType = objsign
-
-# For normal client use this is typical
-# nsCertType = client, email
-
-# and for everything including object signing:
-# nsCertType = client, email, objsign
-
-# This is typical in keyUsage for a client certificate.
-# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
-
-# This will be displayed in Netscape's comment listbox.
-nsComment			= "OpenSSL Generated Certificate"
-
-# PKIX recommendations harmless if included in all certificates.
-subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid,issuer
-
-# This stuff is for subjectAltName and issuerAltname.
-# Import the email address.
-# subjectAltName=email:copy
-# An alternative to produce certificates that aren't
-# deprecated according to PKIX.
-# subjectAltName=email:move
-
-# Copy subject details
-# issuerAltName=issuer:copy
-
-#nsCaRevocationUrl		= http://www.domain.dom/ca-crl.pem
-#nsBaseUrl
-#nsRevocationUrl
-#nsRenewalUrl
-#nsCaPolicyUrl
-#nsSslServerName
-
-[ v3_req ]
-
-# Extensions to add to a certificate request
-
-basicConstraints = CA:FALSE
-keyUsage = nonRepudiation, digitalSignature, keyEncipherment
-extendedKeyUsage = critical,timeStamping,serverAuth,clientAuth
-subjectAltName = @alt_names
-
-[ v3_ca ]
-
-
-# Extensions for a typical CA
-
-
-# PKIX recommendation.
-
-subjectKeyIdentifier=hash
-
-authorityKeyIdentifier=keyid:always,issuer
-
-# This is what PKIX recommends but some broken software chokes on critical
-# extensions.
-#basicConstraints = critical,CA:true
-# So we do this instead.
-basicConstraints = CA:true
-keyUsage = keyCertSign, cRLSign
-
-# Key usage: this is typical for a CA certificate. However since it will
-# prevent it being used as an test self-signed certificate it is best
-# left out by default.
-# keyUsage = cRLSign, keyCertSign
-
-# Some might want this also
-# nsCertType = sslCA, emailCA
-
-# Include email address in subject alt name: another PKIX recommendation
-# subjectAltName=email:copy
-# Copy issuer details
-# issuerAltName=issuer:copy
-
-# DER hex encoding of an extension: beware experts only!
-# obj=DER:02:03
-# Where 'obj' is a standard or added object
-# You can even override a supported extension:
-# basicConstraints= critical, DER:30:03:01:01:FF
-
-[ crl_ext ]
-
-# CRL extensions.
-# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
-
-# issuerAltName=issuer:copy
-authorityKeyIdentifier=keyid:always
-
-[ proxy_cert_ext ]
-# These extensions should be added when creating a proxy certificate
-
-# This goes against PKIX guidelines but some CAs do it and some software
-# requires this to avoid interpreting an end user certificate as a CA.
-
-basicConstraints=CA:FALSE
-
-# Here are some examples of the usage of nsCertType. If it is omitted
-# the certificate can be used for anything *except* object signing.
-
-# This is OK for an SSL server.
-# nsCertType			= server
-
-# For an object signing certificate this would be used.
-# nsCertType = objsign
-
-# For normal client use this is typical
-# nsCertType = client, email
-
-# and for everything including object signing:
-# nsCertType = client, email, objsign
-
-# This is typical in keyUsage for a client certificate.
-# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
-
-# This will be displayed in Netscape's comment listbox.
-nsComment			= "OpenSSL Generated Certificate"
-
-# PKIX recommendations harmless if included in all certificates.
-subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid,issuer
-
-# This stuff is for subjectAltName and issuerAltname.
-# Import the email address.
-# subjectAltName=email:copy
-# An alternative to produce certificates that aren't
-# deprecated according to PKIX.
-# subjectAltName=email:move
-
-# Copy subject details
-# issuerAltName=issuer:copy
-
-#nsCaRevocationUrl		= http://www.domain.dom/ca-crl.pem
-#nsBaseUrl
-#nsRevocationUrl
-#nsRenewalUrl
-#nsCaPolicyUrl
-#nsSslServerName
-
-# This really needs to be in place for it to be a proxy certificate.
-proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
-
-####################################################################
-[ tsa ]
-
-default_tsa = tsa_config1	# the default TSA section
-
-[ tsa_config1 ]
-
-# These are used by the TSA reply generation only.
-dir		= ./demoCA		# TSA root directory
-serial		= $dir/tsaserial	# The current serial number (mandatory)
-crypto_device	= builtin		# OpenSSL engine to use for signing
-signer_cert	= $dir/tsacert.pem 	# The TSA signing certificate
-					# (optional)
-certs		= $dir/cacert.pem	# Certificate chain to include in reply
-					# (optional)
-signer_key	= $dir/private/tsakey.pem # The TSA private key (optional)
-
-default_policy	= tsa_policy1		# Policy if request did not specify it
-					# (optional)
-other_policies	= tsa_policy2, tsa_policy3	# acceptable policies (optional)
-digests		= md5, sha1		# Acceptable message digests (mandatory)
-accuracy	= secs:1, millisecs:500, microsecs:100	# (optional)
-clock_precision_digits  = 0	# number of digits after dot. (optional)
-ordering		= yes	# Is ordering defined for timestamps?
-				# (optional, default: no)
-tsa_name		= yes	# Must the TSA name be included in the reply?
-				# (optional, default: no)
-ess_cert_id_chain	= no	# Must the ESS cert id chain be included?
-				# (optional, default: no)
-
-
-[alt_names]
-IP.1 = 127.0.0.1

+ 57 - 59
fixtures/ca/server-chain.pem

@@ -1,63 +1,61 @@
 -----BEGIN CERTIFICATE-----
-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
+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
 -----END CERTIFICATE-----
 -----BEGIN CERTIFICATE-----
-MIIFfDCCA2SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTET
-MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
-dHkgTHRkMB4XDTEzMTExMzE5MDgxMloXDTE0MTExMzE5MDgxMlowZTELMAkGA1UE
-BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
-ZGdpdHMgUHR5IEx0ZDEeMBwGA1UEAwwVaHR0cDovLzEyNy4wLjAuMTo0MDAxMIIC
-IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1XBtjDav5Sl3H+/fUcGiQO36
-oqtZG+YuC9D0z0u89Shq+XNs3tRtonGGCyEIrDtI6R7PItMJa6rQ1VuFoMWPrjmF
-f5tFemSOZtQx/DF78H+5tWaIBVDA+Kw1zxdqj1n3/AQjAGsSuqhgcaIQQFqTNNtA
-tW40048fDh17jWIDB9baF65az2uArq97uS4deDujG3CHV9svO7hoqpzYt039VDKK
-4N+dDMUZFqhEmY2MqjyQySY2bd/gsYBjcGWSIuonALactiYDc4zIusAfNptnXycw
-K/aQAqDwhwMcQA9L5YKQ5hoUukDTQFbvoNLJ3vNQDI/o8sjCh94EkMuopSp90tJ/
-syTPKRlh/MaGMXvwu8Vab5iPeVop48jTKl3Z+G8NErGM8KKCyd1mQoGisVuIMQPK
-uJUi7jOu6wgXlA8ZgUGfSQQDA4v2Q0tV/GlJmvsP5JshA3v7C/sSBY/3AnPHeWTl
-ozXlNgXitxps1EwgR2jo+YW2gxrfM//xtgMCjMXjO9g2TsCnWR6j93oXWn88UP/C
-eAcyjeTdJjW+piuLdvYOctY6+Yql5gm9Vx0u+w9jTmpzOCoh+9cNtjqmPiOhecFc
-Vf71vMf4krMp6lmY/Nq1/km9u0ViNP5CJHk18YXG42vnj7sUgT7WgpLh8g/iPc2p
-etH9lMd9te+Jyak/zA8CAwEAAaNXMFUwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAw
-KgYDVR0lAQH/BCAwHgYIKwYBBQUHAwgGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNV
-HREECDAGhwR/AAABMA0GCSqGSIb3DQEBBQUAA4ICAQCmF13fF7kAcgCzRKBCpggz
-ObVNSK9btGKUU+xyOZsRgEDWdfeBvG3VzXflLMDv3P45O90CHkcGfckbj1ISctIY
-0OSnCn4vmIwnhwUWyYlCagtNB68GVcPH8XLxn/wsnT2MbiA/ohUStKXQtBmBxIUH
-rI+7r28qcHW7AnT47G5BbSYzW0xYzlkUwyl8rBxRLDKVTHBJ5GmlREJKdAzKS7CS
-6jhcsxa544Rsa7CDvjLKpJO0iploL0/GY+5oj3VdhgdEJgwqwvu3skfd1N8wkxH4
-NQuRmyvXxcMSxKv4vbOOUm4PfOqOwwXiVZLoc29ePUv9zCU2AVdS21DD9zAZeKDb
-B87VWnQKO6JUvL5vX7xsMnsSbnLHGA2kPv4IDZ6jKuZdVneM+whDZlBWpHRL2RKX
-K0JZICf7+EbmrUW3Rwl+dIaIJ55Ni1rfDSZWeIYKFx04Mod6Wbch7ahb/XVvIDe9
-SFjLfeNj7L/Iz0i+1lTarAMxIRC521IwcobhAxqxYCv3oNf6f+tz8DyCCvsTCc9W
-/OLKX7sukh1ohle+0EFrSYpX5PzkHwZRVZjx55KwkIV/KtwBadJv+z6iwW3qOn/A
-/1yC8Mbc2TdCaRPwFO80LAg9cz+XfT5vZoQUvOnOxIrhFnasQ/xiovy0zKCr2Fjg
-ePQ4BNEN9wt3SsPp8ig39g==
+MIIFWzCCA0WgAwIBAgIBAjALBgkqhkiG9w0BAQUwLTEMMAoGA1UEBhMDVVNBMRAw
+DgYDVQQKEwdldGNkLWNhMQswCQYDVQQLEwJDQTAeFw0xNDAzMTMwMjA5MjJaFw0y
+NDAzMTMwMjA5MjJaMEUxDDAKBgNVBAYTA1VTQTEQMA4GA1UEChMHZXRjZC1jYTEP
+MA0GA1UECxMGc2VydmVyMRIwEAYDVQQDEwkxMjcuMC4wLjEwggIiMA0GCSqGSIb3
+DQEBAQUAA4ICDwAwggIKAoICAQDI3EvqJrLWsnPbjAT8ENiMRyBINhhafubi5Nb+
+glEzkbC2kv2zXkVkpkBubDRwyh3eomSbdwKYk3yz+IopT753teJueRpMPq9Ayr/+
+PZl4Y1tG04KcjfOvOls6zPsDfHzluR8TE705If5wwZu3Bdwxzdtx9T0ROzIEgRt0
+Axuce5qkg93IWNxOrIr+4LCxYfTpvpTXO20lz0IuQNm1Opo9PVoWn7PXdOmuCzSG
+2hW1DcKqSyQP7IkplBJS0EhoovIsXavSkPKJssvQj73ZFIBVgKhXuHmPNdrypaQk
+CtxsqbVdOOlojItqYTTDAiadwRQWkYgDOSQCGJiPqYVJx+rH4MlzxQ6n9x2qIcne
+lfMr+VFDEc1YvHu1XLMg5b1ImD6ChutYW0RhFJ3CQVdQR2i4kJ8T1DSJYLISMODZ
+ux1cZaUoSL/EkrC5/8POWZmP8nJXO6A4wrZDHF30/qWpo+T5PvsA6cABfX1jkcTx
+PBXGK1qOZ8rToTxprJ2zc3zuZNxSgM32nzjcPUgn559Mgdl0HR4c4JeTZGsebWmx
+MWmkz//BV4eUaGHqCpzRQHf3YIxysvDC2Xf4z2Alk8AlLRXp7/ksatdxAtyc+y8+
+MWCc6N0YbI9zjv+ezCBqR+mu1P5Tb0HebPFz3dOdIpiC3kU8QyMEagw8u5xliZs4
+AxwdNwIDAQABo3IwcDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYD
+VR0OBBYEFD6UrVN8uolWz6et79jVeZetjd4XMB8GA1UdIwQYMBaAFJ1ZVb5LWPs5
+S6RuN506RgFplJK2MA8GA1UdEQQIMAaHBH8AAAEwCwYJKoZIhvcNAQEFA4ICAQCo
+sKn1Rjx0tIVWAZAZB4lCWvkQDp/txnb5zzQUlKhIW2o98IklASmOYYyZbE2PXlda
+/n8TwKIzWgIoNh5AcgLWhtASrnZdGFXY88n5jGk6CVZ1+Dl+IX99h+r+YHQzf1jU
+BjGrZHGv3pPjwhFGDS99lM/TEBk/eLI2Kx5laL+nWMTwa8M1OwSIh6ZxYPVlWUqb
+rurk5l/YqW+UkYIXIQhe6LwtB7tBjr6nDIWBfHQ7uN8IdB8VIAF6lejr22VmERTW
+j+zJ5eTzuQN1f0s930mEm8pW7KgGxlEqrUlSJtxlMFCv6ZHZk1Y4yEiOCBKlPNme
+X3B+lhj//PH3gLNm3+ZRr5ena3k+wL9Dd3d3GDCIx0ERQyrGS/rJpqNPI+8ZQlG0
+nrFlm7aP6UznESQnJoSFbydiD0EZ4hXSdmDdXQkTklRpeXfMcrYBGN7JrGZOZ2T2
+WtXBMx2bgPeEH50KRrwUMFe122bchh0Fr+hGvNK2Q9/gRyQPiYHq6vSF4GzorzLb
+aDuWA9JRH8/c0z8tMvJ7KjmmmIxd39WWGZqiBrGQR7utOJjpQl+HCsDIQM6yZ/Bu
+RpwKj2yBz0OQg4tWbtqUuFkRMTkCR6vo3PadgO1VWokM7UFUXlScnYswcM5EwnzJ
+/IsYJ2s1V706QVUzAGIbi3+wYi3enk7JfYoGIqa2oA==
 -----END CERTIFICATE-----

+ 29 - 30
fixtures/ca/server.crt

@@ -1,32 +1,31 @@
 -----BEGIN CERTIFICATE-----
-MIIFfDCCA2SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTET
-MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
-dHkgTHRkMB4XDTEzMTExMzE5MDgxMloXDTE0MTExMzE5MDgxMlowZTELMAkGA1UE
-BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
-ZGdpdHMgUHR5IEx0ZDEeMBwGA1UEAwwVaHR0cDovLzEyNy4wLjAuMTo0MDAxMIIC
-IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1XBtjDav5Sl3H+/fUcGiQO36
-oqtZG+YuC9D0z0u89Shq+XNs3tRtonGGCyEIrDtI6R7PItMJa6rQ1VuFoMWPrjmF
-f5tFemSOZtQx/DF78H+5tWaIBVDA+Kw1zxdqj1n3/AQjAGsSuqhgcaIQQFqTNNtA
-tW40048fDh17jWIDB9baF65az2uArq97uS4deDujG3CHV9svO7hoqpzYt039VDKK
-4N+dDMUZFqhEmY2MqjyQySY2bd/gsYBjcGWSIuonALactiYDc4zIusAfNptnXycw
-K/aQAqDwhwMcQA9L5YKQ5hoUukDTQFbvoNLJ3vNQDI/o8sjCh94EkMuopSp90tJ/
-syTPKRlh/MaGMXvwu8Vab5iPeVop48jTKl3Z+G8NErGM8KKCyd1mQoGisVuIMQPK
-uJUi7jOu6wgXlA8ZgUGfSQQDA4v2Q0tV/GlJmvsP5JshA3v7C/sSBY/3AnPHeWTl
-ozXlNgXitxps1EwgR2jo+YW2gxrfM//xtgMCjMXjO9g2TsCnWR6j93oXWn88UP/C
-eAcyjeTdJjW+piuLdvYOctY6+Yql5gm9Vx0u+w9jTmpzOCoh+9cNtjqmPiOhecFc
-Vf71vMf4krMp6lmY/Nq1/km9u0ViNP5CJHk18YXG42vnj7sUgT7WgpLh8g/iPc2p
-etH9lMd9te+Jyak/zA8CAwEAAaNXMFUwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAw
-KgYDVR0lAQH/BCAwHgYIKwYBBQUHAwgGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNV
-HREECDAGhwR/AAABMA0GCSqGSIb3DQEBBQUAA4ICAQCmF13fF7kAcgCzRKBCpggz
-ObVNSK9btGKUU+xyOZsRgEDWdfeBvG3VzXflLMDv3P45O90CHkcGfckbj1ISctIY
-0OSnCn4vmIwnhwUWyYlCagtNB68GVcPH8XLxn/wsnT2MbiA/ohUStKXQtBmBxIUH
-rI+7r28qcHW7AnT47G5BbSYzW0xYzlkUwyl8rBxRLDKVTHBJ5GmlREJKdAzKS7CS
-6jhcsxa544Rsa7CDvjLKpJO0iploL0/GY+5oj3VdhgdEJgwqwvu3skfd1N8wkxH4
-NQuRmyvXxcMSxKv4vbOOUm4PfOqOwwXiVZLoc29ePUv9zCU2AVdS21DD9zAZeKDb
-B87VWnQKO6JUvL5vX7xsMnsSbnLHGA2kPv4IDZ6jKuZdVneM+whDZlBWpHRL2RKX
-K0JZICf7+EbmrUW3Rwl+dIaIJ55Ni1rfDSZWeIYKFx04Mod6Wbch7ahb/XVvIDe9
-SFjLfeNj7L/Iz0i+1lTarAMxIRC521IwcobhAxqxYCv3oNf6f+tz8DyCCvsTCc9W
-/OLKX7sukh1ohle+0EFrSYpX5PzkHwZRVZjx55KwkIV/KtwBadJv+z6iwW3qOn/A
-/1yC8Mbc2TdCaRPwFO80LAg9cz+XfT5vZoQUvOnOxIrhFnasQ/xiovy0zKCr2Fjg
-ePQ4BNEN9wt3SsPp8ig39g==
+MIIFWzCCA0WgAwIBAgIBAjALBgkqhkiG9w0BAQUwLTEMMAoGA1UEBhMDVVNBMRAw
+DgYDVQQKEwdldGNkLWNhMQswCQYDVQQLEwJDQTAeFw0xNDAzMTMwMjA5MjJaFw0y
+NDAzMTMwMjA5MjJaMEUxDDAKBgNVBAYTA1VTQTEQMA4GA1UEChMHZXRjZC1jYTEP
+MA0GA1UECxMGc2VydmVyMRIwEAYDVQQDEwkxMjcuMC4wLjEwggIiMA0GCSqGSIb3
+DQEBAQUAA4ICDwAwggIKAoICAQDI3EvqJrLWsnPbjAT8ENiMRyBINhhafubi5Nb+
+glEzkbC2kv2zXkVkpkBubDRwyh3eomSbdwKYk3yz+IopT753teJueRpMPq9Ayr/+
+PZl4Y1tG04KcjfOvOls6zPsDfHzluR8TE705If5wwZu3Bdwxzdtx9T0ROzIEgRt0
+Axuce5qkg93IWNxOrIr+4LCxYfTpvpTXO20lz0IuQNm1Opo9PVoWn7PXdOmuCzSG
+2hW1DcKqSyQP7IkplBJS0EhoovIsXavSkPKJssvQj73ZFIBVgKhXuHmPNdrypaQk
+CtxsqbVdOOlojItqYTTDAiadwRQWkYgDOSQCGJiPqYVJx+rH4MlzxQ6n9x2qIcne
+lfMr+VFDEc1YvHu1XLMg5b1ImD6ChutYW0RhFJ3CQVdQR2i4kJ8T1DSJYLISMODZ
+ux1cZaUoSL/EkrC5/8POWZmP8nJXO6A4wrZDHF30/qWpo+T5PvsA6cABfX1jkcTx
+PBXGK1qOZ8rToTxprJ2zc3zuZNxSgM32nzjcPUgn559Mgdl0HR4c4JeTZGsebWmx
+MWmkz//BV4eUaGHqCpzRQHf3YIxysvDC2Xf4z2Alk8AlLRXp7/ksatdxAtyc+y8+
+MWCc6N0YbI9zjv+ezCBqR+mu1P5Tb0HebPFz3dOdIpiC3kU8QyMEagw8u5xliZs4
+AxwdNwIDAQABo3IwcDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYD
+VR0OBBYEFD6UrVN8uolWz6et79jVeZetjd4XMB8GA1UdIwQYMBaAFJ1ZVb5LWPs5
+S6RuN506RgFplJK2MA8GA1UdEQQIMAaHBH8AAAEwCwYJKoZIhvcNAQEFA4ICAQCo
+sKn1Rjx0tIVWAZAZB4lCWvkQDp/txnb5zzQUlKhIW2o98IklASmOYYyZbE2PXlda
+/n8TwKIzWgIoNh5AcgLWhtASrnZdGFXY88n5jGk6CVZ1+Dl+IX99h+r+YHQzf1jU
+BjGrZHGv3pPjwhFGDS99lM/TEBk/eLI2Kx5laL+nWMTwa8M1OwSIh6ZxYPVlWUqb
+rurk5l/YqW+UkYIXIQhe6LwtB7tBjr6nDIWBfHQ7uN8IdB8VIAF6lejr22VmERTW
+j+zJ5eTzuQN1f0s930mEm8pW7KgGxlEqrUlSJtxlMFCv6ZHZk1Y4yEiOCBKlPNme
+X3B+lhj//PH3gLNm3+ZRr5ena3k+wL9Dd3d3GDCIx0ERQyrGS/rJpqNPI+8ZQlG0
+nrFlm7aP6UznESQnJoSFbydiD0EZ4hXSdmDdXQkTklRpeXfMcrYBGN7JrGZOZ2T2
+WtXBMx2bgPeEH50KRrwUMFe122bchh0Fr+hGvNK2Q9/gRyQPiYHq6vSF4GzorzLb
+aDuWA9JRH8/c0z8tMvJ7KjmmmIxd39WWGZqiBrGQR7utOJjpQl+HCsDIQM6yZ/Bu
+RpwKj2yBz0OQg4tWbtqUuFkRMTkCR6vo3PadgO1VWokM7UFUXlScnYswcM5EwnzJ
+/IsYJ2s1V706QVUzAGIbi3+wYi3enk7JfYoGIqa2oA==
 -----END CERTIFICATE-----

+ 0 - 30
fixtures/ca/server.csr

@@ -1,30 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIIFEDCCAvgCAQAwZTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
-ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEeMBwGA1UEAwwVaHR0
-cDovLzEyNy4wLjAuMTo0MDAxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
-AgEA1XBtjDav5Sl3H+/fUcGiQO36oqtZG+YuC9D0z0u89Shq+XNs3tRtonGGCyEI
-rDtI6R7PItMJa6rQ1VuFoMWPrjmFf5tFemSOZtQx/DF78H+5tWaIBVDA+Kw1zxdq
-j1n3/AQjAGsSuqhgcaIQQFqTNNtAtW40048fDh17jWIDB9baF65az2uArq97uS4d
-eDujG3CHV9svO7hoqpzYt039VDKK4N+dDMUZFqhEmY2MqjyQySY2bd/gsYBjcGWS
-IuonALactiYDc4zIusAfNptnXycwK/aQAqDwhwMcQA9L5YKQ5hoUukDTQFbvoNLJ
-3vNQDI/o8sjCh94EkMuopSp90tJ/syTPKRlh/MaGMXvwu8Vab5iPeVop48jTKl3Z
-+G8NErGM8KKCyd1mQoGisVuIMQPKuJUi7jOu6wgXlA8ZgUGfSQQDA4v2Q0tV/GlJ
-mvsP5JshA3v7C/sSBY/3AnPHeWTlozXlNgXitxps1EwgR2jo+YW2gxrfM//xtgMC
-jMXjO9g2TsCnWR6j93oXWn88UP/CeAcyjeTdJjW+piuLdvYOctY6+Yql5gm9Vx0u
-+w9jTmpzOCoh+9cNtjqmPiOhecFcVf71vMf4krMp6lmY/Nq1/km9u0ViNP5CJHk1
-8YXG42vnj7sUgT7WgpLh8g/iPc2petH9lMd9te+Jyak/zA8CAwEAAaBmMGQGCSqG
-SIb3DQEJDjFXMFUwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwKgYDVR0lAQH/BCAw
-HgYIKwYBBQUHAwgGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHREECDAGhwR/AAAB
-MA0GCSqGSIb3DQEBBQUAA4ICAQCeVkI4JwSyax5kJJ1iljkngfK+BdsrqBJWPITj
-mrl9DH7/2ev0GdzHEG3oQrti6vMvsowS6vHtpgc1tsbS9UwDY/N0ZgFWgnb5O5k2
-4zZfGkma3GHCvG9WXsA9+Gs7KedggsXxfnJTLe4B/sZqtRO0dMD/JZTJQ6reuayh
-bYvVBVSmittAjsfer+9xuXkHYYAPNYmW52aUN1AhnIsS3TVp1vHcxgNoFOQglN21
-lHwmeh5QbTx/paHFnWLLqLVydbiB/Qzz6x4zsEKESATd02WbN9XKUfGM0G+bG+57
-ErtrU7yzsLjPYYPcP9nYg8dzfdwVgfdjg+yw2hdmkqjDQD3YAmxRcat7uK8htVa0
-z4dfjdNRO3HhSLALKS/Tl9qpLKpEi8/0ByYErJz6i+Xyf4pkdPQcBQKybkFja136
-9xkonhE7DLTo1zQobfAJlnfTMxuJc0mOGvT+DqGSCFmNEl3WgIAgu9m77mp81Bqo
-0qwrB3pYSAzL9xHuluwZMn37sdmVFFReEkEaRllRgDTZL9vSQh2yOqtV920083/y
-sHPUijSsKysSKz0RuzMBCc3RD07Kcs1TFg/NdZiYKf8V9NDDOgk5LC2Sqoin7v6F
-yB4wpnm6RqQ7iSRpp/VBs3PAnK2uJEkoOU5p5jZdQ0IDtesHVzM7bCUIrQIX2pAr
-owvMhQ==
------END CERTIFICATE REQUEST-----

+ 0 - 54
fixtures/ca/server.key

@@ -1,54 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-Proc-Type: 4,ENCRYPTED
-DEK-Info: DES-EDE3-CBC,E445BB698AB9CDC4
-
-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
------END RSA PRIVATE KEY-----

+ 49 - 49
fixtures/ca/server.key.insecure

@@ -1,51 +1,51 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----

+ 29 - 29
fixtures/ca/server2.crt

@@ -1,31 +1,31 @@
 -----BEGIN CERTIFICATE-----
-MIIFXDCCA0SgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTET
-MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
-dHkgTHRkMB4XDTEzMTExMzE5MTg1MFoXDTE0MTExMzE5MTg1MFowRTELMAkGA1UE
-BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
-ZGdpdHMgUHR5IEx0ZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALY5
-FfOgbclrkfZS/XpzPJgWZrs/W+zakuoBDtkeTeIdVMk2lNZ//oA4g+eYVPnf0DSJ
-oEPvJIOuwjF8b2M3iYR+fwV4iI2NKVEiV5469qtjkSm7OlvNRMAeWztGm8uXI9wg
-9MEqVU4SPlpTV7kUE2c/d3jU6ZAjvgBn6sWnRWG39lBGXecqay6PzbAYhlrGH/Ou
-PZaL4mc6nE1D7/mdBOo3J+6dNQUzB9FhU/BY55M5c0tSrDGlM9pPE9/p3JXu1dcp
-95YtZbUCib/NPVnHGEK5s8LfG6RE8kxxUj6bPjPmyEF+jsgwJb2vdHjOXbwbtsji
-WprZ6TrAFfLQWmD9qbsVxvyMlnuzrfPyaREWhgijBADDGBmcL8k/OFH3LqhmwOET
-LpGepCV5BbRqD3+tDIQcjjlt1fdJTDu1RuPs921EdHXaQNzTTPbO9DoNpFY0l6Ul
-Hjlz8VlnwuEoehd3NDZc7a/b8X6Ry0JwuaymouUJE9GQrGJRJ3imZkwrhhqxiWIt
-p6HJKgePD38V1tFbifqjiF/LfhqaZ/oaFU5f3wLbs41BuMOQtEoNsmRCNd2L6BJ+
-+fskqnJfbuw6YRoB7gvoFjyqn5CZuox8ArmOs/oYk2nior2jBEQI+S0JwVquBDvb
-vyUnYT+E/mSURUA6kPtHax0kaDZnQijXGc4KwASpAgMBAAGjVzBVMAkGA1UdEwQC
-MAAwCwYDVR0PBAQDAgXgMCoGA1UdJQEB/wQgMB4GCCsGAQUFBwMIBggrBgEFBQcD
-AQYIKwYBBQUHAwIwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQUFAAOCAgEA
-qZuN6qS3vIXPCOcRAC/3mxLtccfZOg57NKb6NQ1lzkPXdMDp6Bd3TfWyrkj0lm1w
-Ya9fb/G0yVGAI11WW4ifML3SF3sKwd5PwiqCZa2kTTxL2Wes7t01jueZ0J7vDecf
-n++PNkCZjQ4BoSbnCABNyuHoW3GMdZAnlaVdkZHetRN7gwsNvfiCWFHU/slhjN8K
-KgvQi08BWhiIFqUmBhqpDJDSgd8lJi2gBjq4idkYkW1xcpO4Iz+dZRloPB/ZICnl
-dt+Err9MAec6XpKdniJFLil2FALMmATEZXnGZMuXezXJEZKYhx9ZHuZZDuNBhD6Y
-VbOTrpq9F7oWdzqhUCa4y3HKKPt0ZuvP/0nHnGBe/eOPrJANQYpCHw02AXom67eJ
-9PW0PS2YC7O4cy+Y/DOI9FkzRpa+Z6OMSqKu9hkBCLGVad1cb8cLyl0MJpUu2UAV
-55ovFT6owhR/ZSIDEX4VeNwX0PsHIXIUDFE5Xp58tB9QbRp9ZA4c4OAQ2eGQ8Grz
-v2IxJJgmjBIuAZqdW6KDKy2vc1SQZw3uaFOYUEk3f5uAoTiARIVF2qrVGPDQjGbC
-fumWNsjU0ale1EstgS5KdQc2Ox3wHsc84up6QaMfPqKmvSDOd+RgGzRqh5G07bni
-BWHaIF0K+tJNnPYiTX7nbP9dP3SAfvsPylUFVj9fiSI=
+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
 -----END CERTIFICATE-----

+ 0 - 29
fixtures/ca/server2.csr

@@ -1,29 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIIE8DCCAtgCAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
-ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCAiIwDQYJKoZIhvcN
-AQEBBQADggIPADCCAgoCggIBALY5FfOgbclrkfZS/XpzPJgWZrs/W+zakuoBDtke
-TeIdVMk2lNZ//oA4g+eYVPnf0DSJoEPvJIOuwjF8b2M3iYR+fwV4iI2NKVEiV546
-9qtjkSm7OlvNRMAeWztGm8uXI9wg9MEqVU4SPlpTV7kUE2c/d3jU6ZAjvgBn6sWn
-RWG39lBGXecqay6PzbAYhlrGH/OuPZaL4mc6nE1D7/mdBOo3J+6dNQUzB9FhU/BY
-55M5c0tSrDGlM9pPE9/p3JXu1dcp95YtZbUCib/NPVnHGEK5s8LfG6RE8kxxUj6b
-PjPmyEF+jsgwJb2vdHjOXbwbtsjiWprZ6TrAFfLQWmD9qbsVxvyMlnuzrfPyaREW
-hgijBADDGBmcL8k/OFH3LqhmwOETLpGepCV5BbRqD3+tDIQcjjlt1fdJTDu1RuPs
-921EdHXaQNzTTPbO9DoNpFY0l6UlHjlz8VlnwuEoehd3NDZc7a/b8X6Ry0Jwuaym
-ouUJE9GQrGJRJ3imZkwrhhqxiWItp6HJKgePD38V1tFbifqjiF/LfhqaZ/oaFU5f
-3wLbs41BuMOQtEoNsmRCNd2L6BJ++fskqnJfbuw6YRoB7gvoFjyqn5CZuox8ArmO
-s/oYk2nior2jBEQI+S0JwVquBDvbvyUnYT+E/mSURUA6kPtHax0kaDZnQijXGc4K
-wASpAgMBAAGgZjBkBgkqhkiG9w0BCQ4xVzBVMAkGA1UdEwQCMAAwCwYDVR0PBAQD
-AgXgMCoGA1UdJQEB/wQgMB4GCCsGAQUFBwMIBggrBgEFBQcDAQYIKwYBBQUHAwIw
-DwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQUFAAOCAgEAsjhQOckysiSTZaz/
-Ck5APYE13ckBQOxvHqrCvDQSRMqq/yb/eWtItQhCs9u3fKGFGKCefPYwtVdUj6v2
-g4SpXzBblcXNP8SXEtbcNuG4lPNsTc2YbDxuLWmgvLX8on2nq0f+TcGmtvvmCaLz
-tEdLPlQfkzQUlwfaK4kMJ4qzR8eeHxBrhKJyDyzSreZSa/ZXMcKpjJLhXl6oM+ud
-cfi0BO/xOKt/MmGHAxTMNrTFV5HcEWzws+r51sCcIV4BwuELA5rZuG0leaj3CDDy
-3dPkpLp3HObbX3KV6yyCCfEAN4pyoZQNut8i87FITsYbJJy4ld7/PWTvg/JMk96X
-ZACpeI1ERL/Sr3uzFpmlLszwSQ7t/hv5ykFA6d1QLv0u+EX1mL1O/PZ5Lb3moC4t
-7ma3CffPqJpEjvvdXtweol62MI4Rr/1sXyZGR+lvkjcoSY5vzv0j7V4w6/e1In3D
-nHY4vmpuy65w5gGm/s2y2cXRfCc8P4we0+A+QbNAgt3eicdJmWZGcWwlMQI10Nmq
-6Qnp8cpwvFM/iCIajfIAz0fD7G0UEL5ExSFoh6WhFFDOYbTl5MeXUjMaYmemICE2
-r3GVfZxHLvDQzftkq/oSXXiZgLkoYveN0R7VJuUfCgyLOlJL5O8WX2lX8odUKINc
-DcZ/FpV+R/u7WGm2j0tvRA4y9Qc=
------END CERTIFICATE REQUEST-----

+ 0 - 54
fixtures/ca/server2.key

@@ -1,54 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-Proc-Type: 4,ENCRYPTED
-DEK-Info: DES-EDE3-CBC,F332544C4A2552FC
-
-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
------END RSA PRIVATE KEY-----

+ 49 - 49
fixtures/ca/server2.key.insecure

@@ -1,51 +1,51 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----

+ 4 - 9
mod/lock/v2/acquire_handler.go

@@ -165,22 +165,17 @@ func (h *handler) watch(keypath string, index int, closeChan <-chan bool) error
 			return fmt.Errorf("lock watch lookup error: %s", err.Error())
 		}
 		nodes := lockNodes{resp.Node.Nodes}
-		prevIndex := nodes.PrevIndex(index)
+		prevIndex, modifiedIndex := nodes.PrevIndex(index)
 
 		// If there is no previous index then we have the lock.
 		if prevIndex == 0 {
 			return nil
 		}
 
-		// Watch previous index until it's gone.
-		waitIndex := resp.Node.ModifiedIndex
+		// Wait from the last modification of the node.
+		waitIndex := modifiedIndex + 1
 
-		// Since event store has only 1000 histories we should use first node's CreatedIndex if available
-		if firstNode := nodes.First(); firstNode != nil {
-			waitIndex = firstNode.CreatedIndex
-		}
-
-		_, err = h.client.Watch(path.Join(keypath, strconv.Itoa(prevIndex)), waitIndex, false, nil, stopWatchChan)
+		resp, err = h.client.Watch(path.Join(keypath, strconv.Itoa(prevIndex)), uint64(waitIndex), false, nil, stopWatchChan)
 		if err == etcd.ErrWatchStoppedByUser {
 			return fmt.Errorf("lock watch closed")
 		} else if err != nil {

+ 7 - 4
mod/lock/v2/lock_nodes.go

@@ -41,17 +41,20 @@ func (s lockNodes) FindByValue(value string) (*etcd.Node, int) {
 	return nil, 0
 }
 
-// Retrieves the index that occurs before a given index.
-func (s lockNodes) PrevIndex(index int) int {
+// Find the node with the largest index in the lockNodes that is smaller than the given index. Also return the lastModified index of that node.
+func (s lockNodes) PrevIndex(index int) (int, int) {
 	sort.Sort(s)
 
+	// Iterate over each node to find the given index. We keep track of the
+	// previous index on each iteration so we can return it when we match the
+	// index we're looking for.
 	var prevIndex int
 	for _, node := range s.Nodes {
 		idx, _ := strconv.Atoi(path.Base(node.Key))
 		if index == idx {
-			return prevIndex
+			return prevIndex, int(node.ModifiedIndex)
 		}
 		prevIndex = idx
 	}
-	return 0
+	return 0, 0
 }

+ 28 - 2
server/listener.go

@@ -3,9 +3,35 @@ package server
 import (
 	"crypto/tls"
 	"net"
+
+	"github.com/coreos/etcd/log"
 )
 
-func NewListener(addr string) (net.Listener, error) {
+// NewListener creates a net.Listener
+// If the given scheme is "https", it will generate TLS configuration based on TLSInfo.
+// If any error happens, this function will call log.Fatal
+func NewListener(scheme, addr string, tlsInfo *TLSInfo) net.Listener {
+	if scheme == "https" {
+		cfg, err := tlsInfo.ServerConfig()
+		if err != nil {
+			log.Fatal("TLS info error: ", err)
+		}
+
+		l, err := newTLSListener(addr, cfg)
+		if err != nil {
+			log.Fatal("Failed to create TLS listener: ", err)
+		}
+		return l
+	}
+
+	l, err := newListener(addr)
+	if err != nil {
+		log.Fatal("Failed to create listener: ", err)
+	}
+	return l
+}
+
+func newListener(addr string) (net.Listener, error) {
 	if addr == "" {
 		addr = ":http"
 	}
@@ -16,7 +42,7 @@ func NewListener(addr string) (net.Listener, error) {
 	return l, nil
 }
 
-func NewTLSListener(addr string, cfg *tls.Config) (net.Listener, error) {
+func newTLSListener(addr string, cfg *tls.Config) (net.Listener, error) {
 	if addr == "" {
 		addr = ":https"
 	}

+ 18 - 6
server/peer_server.go

@@ -231,6 +231,19 @@ func (s *PeerServer) findCluster(discoverURL string, peers []string) {
 	}
 	peers = append(peers, prevPeers...)
 
+	// Remove its own peer address from the peer list to join
+	u, err := url.Parse(s.Config.URL)
+	if err != nil {
+		log.Fatalf("cannot parse peer address %v: %v", s.Config.URL, err)
+	}
+	filteredPeers := make([]string, 0)
+	for _, v := range peers {
+		if v != u.Host {
+			filteredPeers = append(filteredPeers, v)
+		}
+	}
+	peers = filteredPeers
+
 	// if there is backup peer lists, use it to find cluster
 	if len(peers) > 0 {
 		ok := s.joinCluster(peers)
@@ -371,13 +384,12 @@ func (s *PeerServer) startAsFollower(cluster []string) {
 
 // getVersion fetches the peer version of a cluster.
 func getVersion(t *transporter, versionURL url.URL) (int, error) {
-	resp, req, err := t.Get(versionURL.String())
+	resp, _, err := t.Get(versionURL.String())
 	if err != nil {
 		return 0, err
 	}
 	defer resp.Body.Close()
 
-	t.CancelWhenTimeout(req)
 	body, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
 		return 0, err
@@ -460,10 +472,11 @@ func (s *PeerServer) joinByPeer(server raft.Server, peer string, scheme string)
 	json.NewEncoder(&b).Encode(c)
 
 	joinURL := url.URL{Host: peer, Scheme: scheme, Path: "/v2/admin/machines/" + server.Name()}
-	log.Debugf("Send Join Request to %s", joinURL.String())
+	log.Infof("Send Join Request to %s", joinURL.String())
 
 	req, _ := http.NewRequest("PUT", joinURL.String(), &b)
 	resp, err := t.client.Do(req)
+
 	for {
 		if err != nil {
 			return fmt.Errorf("Unable to join: %v", err)
@@ -471,8 +484,7 @@ func (s *PeerServer) joinByPeer(server raft.Server, peer string, scheme string)
 		if resp != nil {
 			defer resp.Body.Close()
 
-			t.CancelWhenTimeout(req)
-
+			log.Infof("»»»» %d", resp.StatusCode)
 			if resp.StatusCode == http.StatusOK {
 				var msg joinMessageV2
 				if err := json.NewDecoder(resp.Body).Decode(&msg); err != nil {
@@ -500,7 +512,7 @@ func (s *PeerServer) joinByPeer(server raft.Server, peer string, scheme string)
 					EtcdURL:    s.server.URL(),
 				}
 				json.NewEncoder(&b).Encode(c)
-				resp, req, err = t.Post(address, &b)
+				resp, _, err = t.Post(address, &b)
 
 			} else if resp.StatusCode == http.StatusBadRequest {
 				log.Debug("Reach max number peers in the cluster")

+ 48 - 30
server/transporter.go

@@ -11,6 +11,11 @@ import (
 
 	"github.com/coreos/etcd/log"
 	"github.com/coreos/etcd/third_party/github.com/goraft/raft"
+	httpclient "github.com/coreos/etcd/third_party/github.com/mreiferson/go-httpclient"
+)
+
+const (
+	snapshotTimeout = time.Second * 120
 )
 
 // Transporter layer for communication between raft nodes
@@ -20,8 +25,10 @@ type transporter struct {
 	serverStats    *raftServerStats
 	registry       *Registry
 
-	client    *http.Client
-	transport *http.Transport
+	client            *http.Client
+	transport         *httpclient.Transport
+	snapshotClient    *http.Client
+	snapshotTransport *httpclient.Transport
 }
 
 type dialer func(network, addr string) (net.Conn, error)
@@ -30,20 +37,38 @@ type dialer func(network, addr string) (net.Conn, error)
 // Create http or https transporter based on
 // whether the user give the server cert and key
 func NewTransporter(followersStats *raftFollowersStats, serverStats *raftServerStats, registry *Registry, dialTimeout, requestTimeout, responseHeaderTimeout time.Duration) *transporter {
-	tr := &http.Transport{
-		Dial: func(network, addr string) (net.Conn, error) {
-			return net.DialTimeout(network, addr, dialTimeout)
-		},
+	tr := &httpclient.Transport{
 		ResponseHeaderTimeout: responseHeaderTimeout,
+		// This is a workaround for Transport.CancelRequest doesn't work on
+		// HTTPS connections blocked. The patch for it is in progress,
+		// and would be available in Go1.3
+		// More: https://codereview.appspot.com/69280043/
+		ConnectTimeout:   dialTimeout,
+		RequestTimeout:   dialTimeout + responseHeaderTimeout,
+		ReadWriteTimeout: responseHeaderTimeout,
+	}
+
+	// Sending snapshot might take a long time so we use a different HTTP transporter
+	// Timeout is set to 120s (Around 100MB if the bandwidth is 10Mbits/s)
+	// This timeout is not calculated by heartbeat time.
+	// TODO(xiangl1) we can actually calculate the max bandwidth if we know
+	// average RTT.
+	// It should be equal to (TCP max window size/RTT).
+	sTr := &httpclient.Transport{
+		ConnectTimeout:   dialTimeout,
+		RequestTimeout:   snapshotTimeout,
+		ReadWriteTimeout: snapshotTimeout,
 	}
 
 	t := transporter{
-		client:         &http.Client{Transport: tr},
-		transport:      tr,
-		requestTimeout: requestTimeout,
-		followersStats: followersStats,
-		serverStats:    serverStats,
-		registry:       registry,
+		client:            &http.Client{Transport: tr},
+		transport:         tr,
+		snapshotClient:    &http.Client{Transport: sTr},
+		snapshotTransport: sTr,
+		requestTimeout:    requestTimeout,
+		followersStats:    followersStats,
+		serverStats:       serverStats,
+		registry:          registry,
 	}
 
 	return &t
@@ -52,6 +77,9 @@ func NewTransporter(followersStats *raftFollowersStats, serverStats *raftServerS
 func (t *transporter) SetTLSConfig(tlsConf tls.Config) {
 	t.transport.TLSClientConfig = &tlsConf
 	t.transport.DisableCompression = true
+
+	t.snapshotTransport.TLSClientConfig = &tlsConf
+	t.snapshotTransport.DisableCompression = true
 }
 
 // Sends AppendEntries RPCs to a peer when the server is the leader.
@@ -81,7 +109,7 @@ func (t *transporter) SendAppendEntriesRequest(server raft.Server, peer *raft.Pe
 
 	start := time.Now()
 
-	resp, httpRequest, err := t.Post(fmt.Sprintf("%s/log/append", u), &b)
+	resp, _, err := t.Post(fmt.Sprintf("%s/log/append", u), &b)
 
 	end := time.Now()
 
@@ -100,8 +128,6 @@ func (t *transporter) SendAppendEntriesRequest(server raft.Server, peer *raft.Pe
 	if resp != nil {
 		defer resp.Body.Close()
 
-		t.CancelWhenTimeout(httpRequest)
-
 		aeresp := &raft.AppendEntriesResponse{}
 		if _, err = aeresp.Decode(resp.Body); err != nil && err != io.EOF {
 			log.Warn("transporter.ae.decoding.error:", err)
@@ -125,7 +151,7 @@ func (t *transporter) SendVoteRequest(server raft.Server, peer *raft.Peer, req *
 	u, _ := t.registry.PeerURL(peer.Name)
 	log.Debugf("Send Vote from %s to %s", server.Name(), u)
 
-	resp, httpRequest, err := t.Post(fmt.Sprintf("%s/vote", u), &b)
+	resp, _, err := t.Post(fmt.Sprintf("%s/vote", u), &b)
 
 	if err != nil {
 		log.Debugf("Cannot send VoteRequest to %s : %s", u, err)
@@ -134,8 +160,6 @@ func (t *transporter) SendVoteRequest(server raft.Server, peer *raft.Peer, req *
 	if resp != nil {
 		defer resp.Body.Close()
 
-		t.CancelWhenTimeout(httpRequest)
-
 		rvrsp := &raft.RequestVoteResponse{}
 		if _, err = rvrsp.Decode(resp.Body); err != nil && err != io.EOF {
 			log.Warn("transporter.vr.decoding.error:", err)
@@ -158,7 +182,7 @@ func (t *transporter) SendSnapshotRequest(server raft.Server, peer *raft.Peer, r
 	u, _ := t.registry.PeerURL(peer.Name)
 	log.Debugf("Send Snapshot Request from %s to %s", server.Name(), u)
 
-	resp, httpRequest, err := t.Post(fmt.Sprintf("%s/snapshot", u), &b)
+	resp, _, err := t.Post(fmt.Sprintf("%s/snapshot", u), &b)
 
 	if err != nil {
 		log.Debugf("Cannot send Snapshot Request to %s : %s", u, err)
@@ -167,8 +191,6 @@ func (t *transporter) SendSnapshotRequest(server raft.Server, peer *raft.Peer, r
 	if resp != nil {
 		defer resp.Body.Close()
 
-		t.CancelWhenTimeout(httpRequest)
-
 		ssrsp := &raft.SnapshotResponse{}
 		if _, err = ssrsp.Decode(resp.Body); err != nil && err != io.EOF {
 			log.Warn("transporter.ss.decoding.error:", err)
@@ -191,7 +213,7 @@ func (t *transporter) SendSnapshotRecoveryRequest(server raft.Server, peer *raft
 	u, _ := t.registry.PeerURL(peer.Name)
 	log.Debugf("Send Snapshot Recovery from %s to %s", server.Name(), u)
 
-	resp, httpRequest, err := t.Post(fmt.Sprintf("%s/snapshotRecovery", u), &b)
+	resp, err := t.PostSnapshot(fmt.Sprintf("%s/snapshotRecovery", u), &b)
 
 	if err != nil {
 		log.Debugf("Cannot send Snapshot Recovery to %s : %s", u, err)
@@ -200,8 +222,6 @@ func (t *transporter) SendSnapshotRecoveryRequest(server raft.Server, peer *raft
 	if resp != nil {
 		defer resp.Body.Close()
 
-		t.CancelWhenTimeout(httpRequest)
-
 		ssrrsp := &raft.SnapshotRecoveryResponse{}
 		if _, err = ssrrsp.Decode(resp.Body); err != nil && err != io.EOF {
 			log.Warn("transporter.ssr.decoding.error:", err)
@@ -227,10 +247,8 @@ func (t *transporter) Get(urlStr string) (*http.Response, *http.Request, error)
 	return resp, req, err
 }
 
-// Cancel the on fly HTTP transaction when timeout happens.
-func (t *transporter) CancelWhenTimeout(req *http.Request) {
-	go func() {
-		time.Sleep(t.requestTimeout)
-		t.transport.CancelRequest(req)
-	}()
+// PostSnapshot posts a json format snapshot to the given url
+// The underlying HTTP transport has a minute level timeout
+func (t *transporter) PostSnapshot(url string, body io.Reader) (*http.Response, error) {
+	return t.snapshotClient.Post(url, "application/json", body)
 }

+ 2 - 2
server/usage.go

@@ -44,8 +44,8 @@ Peer Communication Options:
   -peer-ca-file=<path>    Path to the peer CA file.
   -peer-cert-file=<path>  Path to the peer cert file.
   -peer-key-file=<path>   Path to the peer key file.
-  -peer-heartbeat-timeout=<time>
-                          Time (in milliseconds) for a heartbeat to timeout.
+  -peer-heartbeat-interval=<time>
+                          Time (in milliseconds) of a heartbeat interval.
   -peer-election-timeout=<time>
                           Time (in milliseconds) for an election to timeout.
 

+ 3 - 4
server/v1/tests/get_handler_test.go

@@ -85,17 +85,15 @@ func TestV1GetKeyDir(t *testing.T) {
 //
 func TestV1WatchKey(t *testing.T) {
 	tests.RunServer(func(s *server.Server) {
-		var body map[string]interface{}
+		var watchResp *http.Response
 		c := make(chan bool)
 		go func() {
-			resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v1/watch/foo/bar"))
-			body = tests.ReadBodyJSON(resp)
+			watchResp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v1/watch/foo/bar"))
 			c <- true
 		}()
 
 		// Make sure response didn't fire early.
 		time.Sleep(1 * time.Millisecond)
-		assert.Nil(t, body, "")
 
 		// Set a value.
 		v := url.Values{}
@@ -113,6 +111,7 @@ func TestV1WatchKey(t *testing.T) {
 			t.Fatal("cannot get watch result")
 		}
 
+		body := tests.ReadBodyJSON(watchResp)
 		assert.NotNil(t, body, "")
 		assert.Equal(t, body["action"], "set", "")
 

+ 1 - 1
server/v1/tests/put_handler_test.go

@@ -151,7 +151,7 @@ func TestV1SetKeyCASOnValueFail(t *testing.T) {
 		body := tests.ReadBodyJSON(resp)
 		assert.Equal(t, body["errorCode"], 101, "")
 		assert.Equal(t, body["message"], "Compare failed", "")
-		assert.Equal(t, body["cause"], "[AAA != XXX] [0 != 2]", "")
+		assert.Equal(t, body["cause"], "[AAA != XXX]", "")
 		assert.Equal(t, body["index"], 2, "")
 	})
 }

+ 3 - 4
server/v2/tests/get_handler_test.go

@@ -90,17 +90,15 @@ func TestV2GetKeyRecursively(t *testing.T) {
 //
 func TestV2WatchKey(t *testing.T) {
 	tests.RunServer(func(s *server.Server) {
-		var body map[string]interface{}
+		var watchResp *http.Response
 		c := make(chan bool)
 		go func() {
-			resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?wait=true"))
-			body = tests.ReadBodyJSON(resp)
+			watchResp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?wait=true"))
 			c <- true
 		}()
 
 		// Make sure response didn't fire early.
 		time.Sleep(1 * time.Millisecond)
-		assert.Nil(t, body, "")
 
 		// Set a value.
 		v := url.Values{}
@@ -118,6 +116,7 @@ func TestV2WatchKey(t *testing.T) {
 			t.Fatal("cannot get watch result")
 		}
 
+		body := tests.ReadBodyJSON(watchResp)
 		assert.NotNil(t, body, "")
 		assert.Equal(t, body["action"], "set", "")
 

+ 80 - 2
server/v2/tests/put_handler_test.go

@@ -239,7 +239,7 @@ func TestV2SetKeyCASOnIndexFail(t *testing.T) {
 		body := tests.ReadBodyJSON(resp)
 		assert.Equal(t, body["errorCode"], 101, "")
 		assert.Equal(t, body["message"], "Compare failed", "")
-		assert.Equal(t, body["cause"], "[ != XXX] [10 != 2]", "")
+		assert.Equal(t, body["cause"], "[10 != 2]", "")
 		assert.Equal(t, body["index"], 2, "")
 	})
 }
@@ -307,7 +307,7 @@ func TestV2SetKeyCASOnValueFail(t *testing.T) {
 		body := tests.ReadBodyJSON(resp)
 		assert.Equal(t, body["errorCode"], 101, "")
 		assert.Equal(t, body["message"], "Compare failed", "")
-		assert.Equal(t, body["cause"], "[AAA != XXX] [0 != 2]", "")
+		assert.Equal(t, body["cause"], "[AAA != XXX]", "")
 		assert.Equal(t, body["index"], 2, "")
 	})
 }
@@ -330,6 +330,84 @@ func TestV2SetKeyCASWithMissingValueFails(t *testing.T) {
 	})
 }
 
+// Ensures that a key is not set if both previous value and index do not match.
+//
+//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
+//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevValue=AAA -d prevIndex=3
+//
+func TestV2SetKeyCASOnValueAndIndexFail(t *testing.T) {
+	tests.RunServer(func(s *server.Server) {
+		v := url.Values{}
+		v.Set("value", "XXX")
+		fullURL := fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar")
+		resp, _ := tests.PutForm(fullURL, v)
+		assert.Equal(t, resp.StatusCode, http.StatusCreated)
+		tests.ReadBody(resp)
+		v.Set("value", "YYY")
+		v.Set("prevValue", "AAA")
+		v.Set("prevIndex", "3")
+		resp, _ = tests.PutForm(fullURL, v)
+		assert.Equal(t, resp.StatusCode, http.StatusPreconditionFailed)
+		body := tests.ReadBodyJSON(resp)
+		assert.Equal(t, body["errorCode"], 101, "")
+		assert.Equal(t, body["message"], "Compare failed", "")
+		assert.Equal(t, body["cause"], "[AAA != XXX] [3 != 2]", "")
+		assert.Equal(t, body["index"], 2, "")
+	})
+}
+
+// Ensures that a key is not set if previous value match but index does not.
+//
+//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
+//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevValue=XXX -d prevIndex=3
+//
+func TestV2SetKeyCASOnValueMatchAndIndexFail(t *testing.T) {
+	tests.RunServer(func(s *server.Server) {
+		v := url.Values{}
+		v.Set("value", "XXX")
+		fullURL := fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar")
+		resp, _ := tests.PutForm(fullURL, v)
+		assert.Equal(t, resp.StatusCode, http.StatusCreated)
+		tests.ReadBody(resp)
+		v.Set("value", "YYY")
+		v.Set("prevValue", "XXX")
+		v.Set("prevIndex", "3")
+		resp, _ = tests.PutForm(fullURL, v)
+		assert.Equal(t, resp.StatusCode, http.StatusPreconditionFailed)
+		body := tests.ReadBodyJSON(resp)
+		assert.Equal(t, body["errorCode"], 101, "")
+		assert.Equal(t, body["message"], "Compare failed", "")
+		assert.Equal(t, body["cause"], "[3 != 2]", "")
+		assert.Equal(t, body["index"], 2, "")
+	})
+}
+
+// Ensures that a key is not set if previous index matches but value does not.
+//
+//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
+//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevValue=AAA -d prevIndex=2
+//
+func TestV2SetKeyCASOnIndexMatchAndValueFail(t *testing.T) {
+	tests.RunServer(func(s *server.Server) {
+		v := url.Values{}
+		v.Set("value", "XXX")
+		fullURL := fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar")
+		resp, _ := tests.PutForm(fullURL, v)
+		assert.Equal(t, resp.StatusCode, http.StatusCreated)
+		tests.ReadBody(resp)
+		v.Set("value", "YYY")
+		v.Set("prevValue", "AAA")
+		v.Set("prevIndex", "2")
+		resp, _ = tests.PutForm(fullURL, v)
+		assert.Equal(t, resp.StatusCode, http.StatusPreconditionFailed)
+		body := tests.ReadBodyJSON(resp)
+		assert.Equal(t, body["errorCode"], 101, "")
+		assert.Equal(t, body["message"], "Compare failed", "")
+		assert.Equal(t, body["cause"], "[AAA != XXX]", "")
+		assert.Equal(t, body["index"], 2, "")
+	})
+}
+
 // Ensure that we can set an empty value
 //
 //   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=

+ 25 - 5
store/node.go

@@ -9,6 +9,14 @@ import (
 	ustrings "github.com/coreos/etcd/pkg/strings"
 )
 
+// explanations of Compare function result
+const (
+	CompareMatch         = 0
+	CompareIndexNotMatch = 1
+	CompareValueNotMatch = 2
+	CompareNotMatch      = 3
+)
+
 var Permanent time.Time
 
 // node is the basic element in the store system.
@@ -321,11 +329,23 @@ func (n *node) UpdateTTL(expireTime time.Time) {
 	}
 }
 
-func (n *node) Compare(prevValue string, prevIndex uint64) bool {
-	compareValue := (prevValue == "" || n.Value == prevValue)
-	compareIndex := (prevIndex == 0 || n.ModifiedIndex == prevIndex)
-
-	return compareValue && compareIndex
+// Compare function compares node index and value with provided ones.
+// second result value explains result and equals to one of Compare.. constants
+func (n *node) Compare(prevValue string, prevIndex uint64) (ok bool, which int) {
+	indexMatch := (prevIndex == 0 || n.ModifiedIndex == prevIndex)
+	valueMatch := (prevValue == "" || n.Value == prevValue)
+	ok = valueMatch && indexMatch
+	switch {
+	case valueMatch && indexMatch:
+		which = CompareMatch
+	case indexMatch && !valueMatch:
+		which = CompareValueNotMatch
+	case valueMatch && !indexMatch:
+		which = CompareIndexNotMatch
+	default:
+		which = CompareNotMatch
+	}
+	return
 }
 
 // Clone function clone the node recursively and return the new node.

+ 16 - 4
store/store.go

@@ -181,6 +181,18 @@ func (s *store) Set(nodePath string, dir bool, value string, expireTime time.Tim
 	return e, nil
 }
 
+// returns user-readable cause of failed comparison
+func getCompareFailCause(n *node, which int, prevValue string, prevIndex uint64) string {
+	switch which {
+	case CompareIndexNotMatch:
+		return fmt.Sprintf("[%v != %v]", prevIndex, n.ModifiedIndex)
+	case CompareValueNotMatch:
+		return fmt.Sprintf("[%v != %v]", prevValue, n.Value)
+	default:
+		return fmt.Sprintf("[%v != %v] [%v != %v]", prevValue, n.Value, prevIndex, n.ModifiedIndex)
+	}
+}
+
 func (s *store) CompareAndSwap(nodePath string, prevValue string, prevIndex uint64,
 	value string, expireTime time.Time) (*Event, error) {
 
@@ -207,8 +219,8 @@ func (s *store) CompareAndSwap(nodePath string, prevValue string, prevIndex uint
 
 	// If both of the prevValue and prevIndex are given, we will test both of them.
 	// Command will be executed, only if both of the tests are successful.
-	if !n.Compare(prevValue, prevIndex) {
-		cause := fmt.Sprintf("[%v != %v] [%v != %v]", prevValue, n.Value, prevIndex, n.ModifiedIndex)
+	if ok, which := n.Compare(prevValue, prevIndex); !ok {
+		cause := getCompareFailCause(n, which, prevValue, prevIndex)
 		s.Stats.Inc(CompareAndSwapFail)
 		return nil, etcdErr.NewError(etcdErr.EcodeTestFailed, cause, s.CurrentIndex)
 	}
@@ -309,8 +321,8 @@ func (s *store) CompareAndDelete(nodePath string, prevValue string, prevIndex ui
 
 	// If both of the prevValue and prevIndex are given, we will test both of them.
 	// Command will be executed, only if both of the tests are successful.
-	if !n.Compare(prevValue, prevIndex) {
-		cause := fmt.Sprintf("[%v != %v] [%v != %v]", prevValue, n.Value, prevIndex, n.ModifiedIndex)
+	if ok, which := n.Compare(prevValue, prevIndex); !ok {
+		cause := getCompareFailCause(n, which, prevValue, prevIndex)
 		s.Stats.Inc(CompareAndDeleteFail)
 		return nil, etcdErr.NewError(etcdErr.EcodeTestFailed, cause, s.CurrentIndex)
 	}

+ 2 - 2
tests/functional/etcd_tls_test.go

@@ -117,8 +117,8 @@ func TestTLSUnauthenticatedClient(t *testing.T) {
 	defer stopServer(proc)
 
 	cacertfile := "../../fixtures/ca/ca.crt"
-	certfile := "../../fixtures/ca/broken/server.crt"
-	keyfile := "../../fixtures/ca/broken/server.key.insecure"
+	certfile := "../../fixtures/ca/broken_server.crt"
+	keyfile := "../../fixtures/ca/broken_server.key.insecure"
 
 	cert, err := tls.LoadX509KeyPair(certfile, keyfile)
 	if err != nil {

+ 78 - 2
tests/functional/multi_node_kill_all_and_recovery_test.go

@@ -14,6 +14,10 @@ func TestMultiNodeKillAllAndRecovery(t *testing.T) {
 	procAttr := new(os.ProcAttr)
 	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
 
+	stop := make(chan bool)
+	leaderChan := make(chan string, 1)
+	all := make(chan bool, 1)
+
 	clusterSize := 5
 	argGroup, etcds, err := CreateCluster(clusterSize, procAttr, false)
 	defer DestroyCluster(etcds)
@@ -24,9 +28,12 @@ func TestMultiNodeKillAllAndRecovery(t *testing.T) {
 
 	c := etcd.NewClient(nil)
 
-	c.SyncCluster()
+	go Monitor(clusterSize, clusterSize, leaderChan, all, stop)
+	<-all
+	<-leaderChan
+	stop <-true
 
-	time.Sleep(time.Second)
+	c.SyncCluster()
 
 	// send 10 commands
 	for i := 0; i < 10; i++ {
@@ -44,10 +51,79 @@ func TestMultiNodeKillAllAndRecovery(t *testing.T) {
 
 	time.Sleep(time.Second)
 
+	stop = make(chan bool)
+	leaderChan = make(chan string, 1)
+	all = make(chan bool, 1)
+
+	time.Sleep(time.Second)
+
+	for i := 0; i < clusterSize; i++ {
+		etcds[i], err = os.StartProcess(EtcdBinPath, argGroup[i], procAttr)
+	}
+
+	go Monitor(clusterSize, 1, leaderChan, all, stop)
+
+	<-all
+	<-leaderChan
+
+	result, err := c.Set("foo", "bar", 0)
+
+	if err != nil {
+		t.Fatalf("Recovery error: %s", err)
+	}
+
+	if result.Node.ModifiedIndex != 16 {
+		t.Fatalf("recovery failed! [%d/16]", result.Node.ModifiedIndex)
+	}
+}
+
+// TestTLSMultiNodeKillAllAndRecovery create a five nodes
+// then kill all the nodes and restart
+func TestTLSMultiNodeKillAllAndRecovery(t *testing.T) {
+	procAttr := new(os.ProcAttr)
+	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
+
 	stop := make(chan bool)
 	leaderChan := make(chan string, 1)
 	all := make(chan bool, 1)
 
+	clusterSize := 5
+	argGroup, etcds, err := CreateCluster(clusterSize, procAttr, true)
+	defer DestroyCluster(etcds)
+
+	if err != nil {
+		t.Fatal("cannot create cluster")
+	}
+
+	c := etcd.NewClient(nil)
+
+	go Monitor(clusterSize, clusterSize, leaderChan, all, stop)
+	<-all
+	<-leaderChan
+	stop <-true
+
+	c.SyncCluster()
+
+	// send 10 commands
+	for i := 0; i < 10; i++ {
+		// Test Set
+		_, err := c.Set("foo", "bar", 0)
+		if err != nil {
+			panic(err)
+		}
+	}
+
+	time.Sleep(time.Second)
+
+	// kill all
+	DestroyCluster(etcds)
+
+	time.Sleep(time.Second)
+
+	stop = make(chan bool)
+	leaderChan = make(chan string, 1)
+	all = make(chan bool, 1)
+
 	time.Sleep(time.Second)
 
 	for i := 0; i < clusterSize; i++ {

+ 1 - 1
tests/http_utils.go

@@ -30,7 +30,7 @@ func ReadBodyJSON(resp *http.Response) map[string]interface{} {
 	m := make(map[string]interface{})
 	b := ReadBody(resp)
 	if err := json.Unmarshal(b, &m); err != nil {
-		panic(fmt.Sprintf("HTTP body JSON parse error: %v", err))
+		panic(fmt.Sprintf("HTTP body JSON parse error: %v: %s", err, string(b)))
 	}
 	return m
 }

+ 12 - 18
tests/server_utils.go

@@ -15,12 +15,12 @@ import (
 )
 
 const (
-	testName             = "ETCDTEST"
-	testClientURL        = "localhost:4401"
-	testRaftURL          = "localhost:7701"
-	testSnapshotCount    = 10000
-	testHeartbeatTimeout = time.Duration(50) * time.Millisecond
-	testElectionTimeout  = time.Duration(200) * time.Millisecond
+	testName              = "ETCDTEST"
+	testClientURL         = "localhost:4401"
+	testRaftURL           = "localhost:7701"
+	testSnapshotCount     = 10000
+	testHeartbeatInterval = time.Duration(50) * time.Millisecond
+	testElectionTimeout   = time.Duration(200) * time.Millisecond
 )
 
 // Starts a server in a temporary directory.
@@ -44,28 +44,22 @@ func RunServer(f func(*server.Server)) {
 	mb := metrics.NewBucket("")
 
 	ps := server.NewPeerServer(psConfig, registry, store, &mb, followersStats, serverStats)
-	psListener, err := server.NewListener(testRaftURL)
-	if err != nil {
-		panic(err)
-	}
+	psListener := server.NewListener("http", testRaftURL, nil)
 
 	// Create Raft transporter and server
-	dialTimeout := (3 * testHeartbeatTimeout) + testElectionTimeout
-	responseHeaderTimeout := (3 * testHeartbeatTimeout) + testElectionTimeout
-	raftTransporter := server.NewTransporter(followersStats, serverStats, registry, testHeartbeatTimeout, dialTimeout, responseHeaderTimeout)
+	dialTimeout := (3 * testHeartbeatInterval) + testElectionTimeout
+	responseHeaderTimeout := (3 * testHeartbeatInterval) + testElectionTimeout
+	raftTransporter := server.NewTransporter(followersStats, serverStats, registry, testHeartbeatInterval, dialTimeout, responseHeaderTimeout)
 	raftServer, err := raft.NewServer(testName, path, raftTransporter, store, ps, "")
 	if err != nil {
 		panic(err)
 	}
 	raftServer.SetElectionTimeout(testElectionTimeout)
-	raftServer.SetHeartbeatInterval(testHeartbeatTimeout)
+	raftServer.SetHeartbeatInterval(testHeartbeatInterval)
 	ps.SetRaftServer(raftServer)
 
 	s := server.New(testName, "http://"+testClientURL, ps, registry, store, nil)
-	sListener, err := server.NewListener(testClientURL)
-	if err != nil {
-		panic(err)
-	}
+	sListener := server.NewListener("http", testClientURL, nil)
 
 	ps.SetServer(s)
 

+ 17 - 0
third_party/github.com/mreiferson/go-httpclient/LICENSE

@@ -0,0 +1,17 @@
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 39 - 0
third_party/github.com/mreiferson/go-httpclient/README.md

@@ -0,0 +1,39 @@
+## go-httpclient
+
+**requires Go 1.1+** as of `v0.4.0` the API has been completely re-written for Go 1.1 (for a Go
+1.0.x compatible release see [1adef50](https://github.com/mreiferson/go-httpclient/tree/1adef50))
+
+[![Build
+Status](https://secure.travis-ci.org/mreiferson/go-httpclient.png?branch=master)](http://travis-ci.org/mreiferson/go-httpclient)
+
+Provides an HTTP Transport that implements the `RoundTripper` interface and
+can be used as a built in replacement for the standard library's, providing:
+
+ * connection timeouts
+ * request timeouts
+
+This is a thin wrapper around `http.Transport` that sets dial timeouts and uses
+Go's internal timer scheduler to call the Go 1.1+ `CancelRequest()` API.
+
+### Example
+
+```go
+transport := &httpclient.Transport{
+    ConnectTimeout:        1*time.Second,
+    RequestTimeout:        10*time.Second,
+    ResponseHeaderTimeout: 5*time.Second,
+}
+defer transport.Close()
+
+client := &http.Client{Transport: transport}
+req, _ := http.NewRequest("GET", "http://127.0.0.1/test", nil)
+resp, err := client.Do(req)
+if err != nil {
+    return err
+}
+defer resp.Body.Close()
+```
+
+### Reference Docs
+
+For API docs see [godoc](http://godoc.org/github.com/mreiferson/go-httpclient).

+ 210 - 0
third_party/github.com/mreiferson/go-httpclient/httpclient.go

@@ -0,0 +1,210 @@
+/*
+Provides an HTTP Transport that implements the `RoundTripper` interface and
+can be used as a built in replacement for the standard library's, providing:
+
+	* connection timeouts
+	* request timeouts
+
+This is a thin wrapper around `http.Transport` that sets dial timeouts and uses
+Go's internal timer scheduler to call the Go 1.1+ `CancelRequest()` API.
+*/
+package httpclient
+
+import (
+	"crypto/tls"
+	"io"
+	"net"
+	"net/http"
+	"net/url"
+	"sync"
+	"time"
+)
+
+// returns the current version of the package
+func Version() string {
+	return "0.4.1"
+}
+
+// Transport implements the RoundTripper interface and can be used as a replacement
+// for Go's built in http.Transport implementing end-to-end request timeouts.
+//
+// 	transport := &httpclient.Transport{
+// 	    ConnectTimeout: 1*time.Second,
+// 	    ResponseHeaderTimeout: 5*time.Second,
+// 	    RequestTimeout: 10*time.Second,
+// 	}
+// 	defer transport.Close()
+//
+// 	client := &http.Client{Transport: transport}
+// 	req, _ := http.NewRequest("GET", "http://127.0.0.1/test", nil)
+// 	resp, err := client.Do(req)
+// 	if err != nil {
+// 	    return err
+// 	}
+// 	defer resp.Body.Close()
+//
+type Transport struct {
+	// Proxy specifies a function to return a proxy for a given
+	// *http.Request. If the function returns a non-nil error, the
+	// request is aborted with the provided error.
+	// If Proxy is nil or returns a nil *url.URL, no proxy is used.
+	Proxy func(*http.Request) (*url.URL, error)
+
+	// Dial specifies the dial function for creating TCP
+	// connections. This will override the Transport's ConnectTimeout and
+	// ReadWriteTimeout settings.
+	// If Dial is nil, a dialer is generated on demand matching the Transport's
+	// options.
+	Dial func(network, addr string) (net.Conn, error)
+
+	// TLSClientConfig specifies the TLS configuration to use with
+	// tls.Client. If nil, the default configuration is used.
+	TLSClientConfig *tls.Config
+
+	// DisableKeepAlives, if true, prevents re-use of TCP connections
+	// between different HTTP requests.
+	DisableKeepAlives bool
+
+	// DisableCompression, if true, prevents the Transport from
+	// requesting compression with an "Accept-Encoding: gzip"
+	// request header when the Request contains no existing
+	// Accept-Encoding value. If the Transport requests gzip on
+	// its own and gets a gzipped response, it's transparently
+	// decoded in the Response.Body. However, if the user
+	// explicitly requested gzip it is not automatically
+	// uncompressed.
+	DisableCompression bool
+
+	// MaxIdleConnsPerHost, if non-zero, controls the maximum idle
+	// (keep-alive) to keep per-host.  If zero,
+	// http.DefaultMaxIdleConnsPerHost is used.
+	MaxIdleConnsPerHost int
+
+	// ConnectTimeout, if non-zero, is the maximum amount of time a dial will wait for
+	// a connect to complete.
+	ConnectTimeout time.Duration
+
+	// ResponseHeaderTimeout, if non-zero, specifies the amount of
+	// time to wait for a server's response headers after fully
+	// writing the request (including its body, if any). This
+	// time does not include the time to read the response body.
+	ResponseHeaderTimeout time.Duration
+
+	// RequestTimeout, if non-zero, specifies the amount of time for the entire
+	// request to complete (including all of the above timeouts + entire response body).
+	// This should never be less than the sum total of the above two timeouts.
+	RequestTimeout time.Duration
+
+	// ReadWriteTimeout, if non-zero, will set a deadline for every Read and
+	// Write operation on the request connection.
+	ReadWriteTimeout time.Duration
+
+	starter   sync.Once
+	transport *http.Transport
+}
+
+// Close cleans up the Transport, currently a no-op
+func (t *Transport) Close() error {
+	return nil
+}
+
+func (t *Transport) lazyStart() {
+	if t.Dial == nil {
+		t.Dial = func(netw, addr string) (net.Conn, error) {
+			c, err := net.DialTimeout(netw, addr, t.ConnectTimeout)
+			if err != nil {
+				return nil, err
+			}
+
+			if t.ReadWriteTimeout > 0 {
+				timeoutConn := &rwTimeoutConn{
+					TCPConn:   c.(*net.TCPConn),
+					rwTimeout: t.ReadWriteTimeout,
+				}
+				return timeoutConn, nil
+			}
+			return c, nil
+		}
+	}
+
+	t.transport = &http.Transport{
+		Dial:                  t.Dial,
+		Proxy:                 t.Proxy,
+		TLSClientConfig:       t.TLSClientConfig,
+		DisableKeepAlives:     t.DisableKeepAlives,
+		DisableCompression:    t.DisableCompression,
+		MaxIdleConnsPerHost:   t.MaxIdleConnsPerHost,
+		ResponseHeaderTimeout: t.ResponseHeaderTimeout,
+	}
+}
+
+func (t *Transport) CancelRequest(req *http.Request) {
+	t.starter.Do(t.lazyStart)
+
+	t.transport.CancelRequest(req)
+}
+
+func (t *Transport) CloseIdleConnections() {
+	t.starter.Do(t.lazyStart)
+
+	t.transport.CloseIdleConnections()
+}
+
+func (t *Transport) RegisterProtocol(scheme string, rt http.RoundTripper) {
+	t.starter.Do(t.lazyStart)
+
+	t.transport.RegisterProtocol(scheme, rt)
+}
+
+func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
+	t.starter.Do(t.lazyStart)
+
+	if t.RequestTimeout > 0 {
+		timer := time.AfterFunc(t.RequestTimeout, func() {
+			t.transport.CancelRequest(req)
+		})
+
+		resp, err = t.transport.RoundTrip(req)
+		if err != nil {
+			timer.Stop()
+		} else {
+			resp.Body = &bodyCloseInterceptor{ReadCloser: resp.Body, timer: timer}
+		}
+	} else {
+		resp, err = t.transport.RoundTrip(req)
+	}
+
+	return
+}
+
+type bodyCloseInterceptor struct {
+	io.ReadCloser
+	timer *time.Timer
+}
+
+func (bci *bodyCloseInterceptor) Close() error {
+	bci.timer.Stop()
+	return bci.ReadCloser.Close()
+}
+
+// A net.Conn that sets a deadline for every Read or Write operation
+type rwTimeoutConn struct {
+	*net.TCPConn
+	rwTimeout time.Duration
+}
+
+func (c *rwTimeoutConn) Read(b []byte) (int, error) {
+	err := c.TCPConn.SetReadDeadline(time.Now().Add(c.rwTimeout))
+	if err != nil {
+		return 0, err
+	}
+	return c.TCPConn.Read(b)
+}
+
+func (c *rwTimeoutConn) Write(b []byte) (int, error) {
+	err := c.TCPConn.SetWriteDeadline(time.Now().Add(c.rwTimeout))
+	if err != nil {
+		return 0, err
+	}
+	return c.TCPConn.Write(b)
+}

+ 233 - 0
third_party/github.com/mreiferson/go-httpclient/httpclient_test.go

@@ -0,0 +1,233 @@
+package httpclient
+
+import (
+	"crypto/tls"
+	"io"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"sync"
+	"testing"
+	"time"
+)
+
+var starter sync.Once
+var addr net.Addr
+
+func testHandler(w http.ResponseWriter, req *http.Request) {
+	time.Sleep(200 * time.Millisecond)
+	io.WriteString(w, "hello, world!\n")
+}
+
+func postHandler(w http.ResponseWriter, req *http.Request) {
+	ioutil.ReadAll(req.Body)
+	w.Header().Set("Content-Length", "2")
+	io.WriteString(w, "OK")
+}
+
+func closeHandler(w http.ResponseWriter, req *http.Request) {
+	hj, _ := w.(http.Hijacker)
+	conn, bufrw, _ := hj.Hijack()
+	defer conn.Close()
+	bufrw.WriteString("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n")
+	bufrw.Flush()
+}
+
+func redirectHandler(w http.ResponseWriter, req *http.Request) {
+	ioutil.ReadAll(req.Body)
+	http.Redirect(w, req, "/post", 302)
+}
+
+func redirect2Handler(w http.ResponseWriter, req *http.Request) {
+	ioutil.ReadAll(req.Body)
+	http.Redirect(w, req, "/redirect", 302)
+}
+
+func slowHandler(w http.ResponseWriter, r *http.Request) {
+	w.WriteHeader(200)
+	io.WriteString(w, "START\n")
+	f := w.(http.Flusher)
+	f.Flush()
+	time.Sleep(200 * time.Millisecond)
+	io.WriteString(w, "WORKING\n")
+	f.Flush()
+	time.Sleep(200 * time.Millisecond)
+	io.WriteString(w, "DONE\n")
+	return
+}
+
+func setupMockServer(t *testing.T) {
+	http.HandleFunc("/test", testHandler)
+	http.HandleFunc("/post", postHandler)
+	http.HandleFunc("/redirect", redirectHandler)
+	http.HandleFunc("/redirect2", redirect2Handler)
+	http.HandleFunc("/close", closeHandler)
+	http.HandleFunc("/slow", slowHandler)
+	ln, err := net.Listen("tcp", ":0")
+	if err != nil {
+		t.Fatalf("failed to listen - %s", err.Error())
+	}
+	go func() {
+		err = http.Serve(ln, nil)
+		if err != nil {
+			t.Fatalf("failed to start HTTP server - %s", err.Error())
+		}
+	}()
+	addr = ln.Addr()
+}
+
+func TestHttpsConnection(t *testing.T) {
+	transport := &Transport{
+		ConnectTimeout: 1 * time.Second,
+		RequestTimeout: 2 * time.Second,
+		TLSClientConfig: &tls.Config{
+			InsecureSkipVerify: true,
+		},
+	}
+	defer transport.Close()
+	client := &http.Client{Transport: transport}
+
+	req, _ := http.NewRequest("GET", "https://httpbin.org/ip", nil)
+	resp, err := client.Do(req)
+	if err != nil {
+		t.Fatalf("1st request failed - %s", err.Error())
+	}
+	_, err = ioutil.ReadAll(resp.Body)
+	if err != nil {
+		t.Fatalf("1st failed to read body - %s", err.Error())
+	}
+	resp.Body.Close()
+
+	req2, _ := http.NewRequest("GET", "https://httpbin.org/delay/5", nil)
+	_, err = client.Do(req2)
+	if err == nil {
+		t.Fatalf("HTTPS request should have timed out")
+	}
+}
+
+func TestHttpClient(t *testing.T) {
+	starter.Do(func() { setupMockServer(t) })
+
+	transport := &Transport{
+		ConnectTimeout:   1 * time.Second,
+		RequestTimeout:   5 * time.Second,
+		ReadWriteTimeout: 3 * time.Second,
+	}
+	client := &http.Client{Transport: transport}
+
+	req, _ := http.NewRequest("GET", "http://"+addr.String()+"/test", nil)
+	resp, err := client.Do(req)
+	if err != nil {
+		t.Fatalf("1st request failed - %s", err.Error())
+	}
+	_, err = ioutil.ReadAll(resp.Body)
+	if err != nil {
+		t.Fatalf("1st failed to read body - %s", err.Error())
+	}
+	resp.Body.Close()
+	transport.Close()
+
+	transport = &Transport{
+		ConnectTimeout:   25 * time.Millisecond,
+		RequestTimeout:   50 * time.Millisecond,
+		ReadWriteTimeout: 50 * time.Millisecond,
+	}
+	client = &http.Client{Transport: transport}
+
+	req2, _ := http.NewRequest("GET", "http://"+addr.String()+"/test", nil)
+	resp, err = client.Do(req2)
+	if err == nil {
+		t.Fatal("2nd request should have timed out")
+	}
+	transport.Close()
+
+	transport = &Transport{
+		ConnectTimeout:   25 * time.Millisecond,
+		RequestTimeout:   250 * time.Millisecond,
+		ReadWriteTimeout: 250 * time.Millisecond,
+	}
+	client = &http.Client{Transport: transport}
+
+	req3, _ := http.NewRequest("GET", "http://"+addr.String()+"/test", nil)
+	resp, err = client.Do(req3)
+	if err != nil {
+		t.Fatal("3rd request should not have timed out")
+	}
+	resp.Body.Close()
+	transport.Close()
+}
+
+func TestSlowServer(t *testing.T) {
+	starter.Do(func() { setupMockServer(t) })
+
+	transport := &Transport{
+		ConnectTimeout:   25 * time.Millisecond,
+		RequestTimeout:   500 * time.Millisecond,
+		ReadWriteTimeout: 250 * time.Millisecond,
+	}
+
+	client := &http.Client{Transport: transport}
+
+	req, _ := http.NewRequest("GET", "http://"+addr.String()+"/slow", nil)
+	resp, err := client.Do(req)
+	if err != nil {
+		t.Fatalf("1st request failed - %s", err)
+	}
+	_, err = ioutil.ReadAll(resp.Body)
+	if err != nil {
+		t.Fatalf("1st failed to read body - %s", err)
+	}
+	resp.Body.Close()
+	transport.Close()
+
+	transport = &Transport{
+		ConnectTimeout:   25 * time.Millisecond,
+		RequestTimeout:   500 * time.Millisecond,
+		ReadWriteTimeout: 100 * time.Millisecond,
+	}
+	client = &http.Client{Transport: transport}
+
+	req, _ = http.NewRequest("GET", "http://"+addr.String()+"/slow", nil)
+	resp, err = client.Do(req)
+	if err != nil {
+		t.Fatalf("2nd request failed - %s", err)
+	}
+	_, err = ioutil.ReadAll(resp.Body)
+	netErr, ok := err.(net.Error)
+	if !ok {
+		t.Fatalf("2nd request dind't return a net.Error - %s", netErr)
+	}
+
+	if !netErr.Timeout() {
+		t.Fatalf("2nd request should have timed out - %s", netErr)
+	}
+
+	resp.Body.Close()
+	transport.Close()
+}
+
+func TestMultipleRequests(t *testing.T) {
+	starter.Do(func() { setupMockServer(t) })
+
+	transport := &Transport{
+		ConnectTimeout:        1 * time.Second,
+		RequestTimeout:        5 * time.Second,
+		ReadWriteTimeout:      3 * time.Second,
+		ResponseHeaderTimeout: 400 * time.Millisecond,
+	}
+	client := &http.Client{Transport: transport}
+
+	req, _ := http.NewRequest("GET", "http://"+addr.String()+"/test", nil)
+	for i := 0; i < 10; i++ {
+		resp, err := client.Do(req)
+		if err != nil {
+			t.Fatalf("%d request failed - %s", i, err.Error())
+		}
+		_, err = ioutil.ReadAll(resp.Body)
+		if err != nil {
+			t.Fatalf("%d failed to read body - %s", i, err.Error())
+		}
+		resp.Body.Close()
+	}
+	transport.Close()
+}