Browse Source

Merge pull request #218 from benbjohnson/refactoring

[wip] Refactoring
Xiang Li 12 years ago
parent
commit
2b291aabea
69 changed files with 5450 additions and 2236 deletions
  1. 1 1
      .gitignore
  2. 1 1
      build
  3. 1 1
      build.ps1
  4. 0 307
      command.go
  5. 11 8
      config.go
  6. 26 68
      etcd.go
  7. 0 250
      etcd_handler_v1.go
  8. 0 364
      etcd_handlers.go
  9. 0 44
      etcd_server.go
  10. 12 14
      etcd_test.go
  11. 44 0
      log/log.go
  12. 0 45
      machines.go
  13. 0 74
      name_url_map.go
  14. 0 147
      raft_handlers.go
  15. 0 329
      raft_server.go
  16. 0 210
      raft_stats.go
  17. 1 1
      scripts/release-version
  18. 75 0
      server/join_command.go
  19. 25 0
      server/package_stats.go
  20. 543 0
      server/peer_server.go
  21. 56 0
      server/raft_follower_stats.go
  22. 55 0
      server/raft_server_stats.go
  23. 197 0
      server/registry.go
  24. 67 0
      server/remove_command.go
  25. 320 0
      server/server.go
  26. 89 0
      server/stats_queue.go
  27. 15 0
      server/timeout.go
  28. 11 0
      server/tls_config.go
  29. 7 0
      server/tls_info.go
  30. 21 27
      server/transporter.go
  31. 1 1
      server/transporter_test.go
  32. 26 0
      server/util.go
  33. 15 0
      server/v1/delete_key_handler.go
  34. 27 0
      server/v1/get_key_handler.go
  35. 51 0
      server/v1/set_key_handler.go
  36. 15 0
      server/v1/v1.go
  37. 40 0
      server/v1/watch_key_handler.go
  38. 29 0
      server/v2/create_key_handler.go
  39. 20 0
      server/v2/delete_key_handler.go
  40. 69 0
      server/v2/get_key_handler.go
  41. 64 0
      server/v2/update_key_handler.go
  42. 18 0
      server/v2/v2.go
  43. 3 3
      server/version.go
  44. 0 36
      snapshot.go
  45. 39 0
      store/create_command.go
  46. 35 0
      store/delete_command.go
  47. 30 125
      store/event.go
  48. 112 0
      store/event_history.go
  49. 25 0
      store/event_queue.go
  50. 41 0
      store/test_and_set_command.go
  51. 20 0
      store/ttl.go
  52. 38 0
      store/update_command.go
  53. 2 0
      third_party/deps
  54. 0 1
      third_party/github.com/coreos/go-etcd/etcd/client.go
  55. 27 0
      third_party/github.com/gorilla/context/LICENSE
  56. 6 0
      third_party/github.com/gorilla/context/README.md
  57. 112 0
      third_party/github.com/gorilla/context/context.go
  58. 66 0
      third_party/github.com/gorilla/context/context_test.go
  59. 82 0
      third_party/github.com/gorilla/context/doc.go
  60. 27 0
      third_party/github.com/gorilla/mux/LICENSE
  61. 6 0
      third_party/github.com/gorilla/mux/README.md
  62. 21 0
      third_party/github.com/gorilla/mux/bench_test.go
  63. 199 0
      third_party/github.com/gorilla/mux/doc.go
  64. 339 0
      third_party/github.com/gorilla/mux/mux.go
  65. 755 0
      third_party/github.com/gorilla/mux/mux_test.go
  66. 758 0
      third_party/github.com/gorilla/mux/old_test.go
  67. 247 0
      third_party/github.com/gorilla/mux/regexp.go
  68. 499 0
      third_party/github.com/gorilla/mux/route.go
  69. 8 179
      util.go

+ 1 - 1
.gitignore

@@ -1,5 +1,5 @@
 src/
 src/
 pkg/
 pkg/
 /etcd
 /etcd
-release_version.go
+/server/release_version.go
 /machine*
 /machine*

+ 1 - 1
build

@@ -21,5 +21,5 @@ for i in third_party/*; do
 	cp -R "$i" src/
 	cp -R "$i" src/
 done
 done
 
 
-./scripts/release-version > release_version.go
+./scripts/release-version > server/release_version.go
 go build "${ETCD_PACKAGE}"
 go build "${ETCD_PACKAGE}"

+ 1 - 1
build.ps1

@@ -20,5 +20,5 @@ foreach($i in (ls third_party/*)){
 	cp -Recurse -force "$i" src/
 	cp -Recurse -force "$i" src/
 }
 }
 
 
-./scripts/release-version.ps1 | Out-File -Encoding UTF8 release_version.go
+./scripts/release-version.ps1 | Out-File -Encoding UTF8 server/release_version.go
 go build -v "${ETCD_PACKAGE}"
 go build -v "${ETCD_PACKAGE}"

+ 0 - 307
command.go

@@ -1,307 +0,0 @@
-package main
-
-import (
-	"encoding/binary"
-	"fmt"
-	"os"
-	"path"
-	"time"
-
-	etcdErr "github.com/coreos/etcd/error"
-	"github.com/coreos/etcd/store"
-	"github.com/coreos/go-raft"
-)
-
-const commandPrefix = "etcd:"
-
-func commandName(name string) string {
-	return commandPrefix + name
-}
-
-// A command represents an action to be taken on the replicated state machine.
-type Command interface {
-	CommandName() string
-	Apply(server *raft.Server) (interface{}, error)
-}
-
-// Create command
-type CreateCommand struct {
-	Key               string    `json:"key"`
-	Value             string    `json:"value"`
-	ExpireTime        time.Time `json:"expireTime"`
-	IncrementalSuffix bool      `json:"incrementalSuffix"`
-	Force             bool      `json:"force"`
-}
-
-// The name of the create command in the log
-func (c *CreateCommand) CommandName() string {
-	return commandName("create")
-}
-
-// Create node
-func (c *CreateCommand) Apply(server *raft.Server) (interface{}, error) {
-	s, _ := server.StateMachine().(*store.Store)
-
-	e, err := s.Create(c.Key, c.Value, c.IncrementalSuffix, c.Force, c.ExpireTime, server.CommitIndex(), server.Term())
-
-	if err != nil {
-		debug(err)
-		return nil, err
-	}
-
-	return e, nil
-}
-
-// Update command
-type UpdateCommand struct {
-	Key        string    `json:"key"`
-	Value      string    `json:"value"`
-	ExpireTime time.Time `json:"expireTime"`
-}
-
-// The name of the update command in the log
-func (c *UpdateCommand) CommandName() string {
-	return commandName("update")
-}
-
-// Update node
-func (c *UpdateCommand) Apply(server *raft.Server) (interface{}, error) {
-	s, _ := server.StateMachine().(*store.Store)
-
-	e, err := s.Update(c.Key, c.Value, c.ExpireTime, server.CommitIndex(), server.Term())
-
-	if err != nil {
-		debug(err)
-		return nil, err
-	}
-
-	return e, nil
-}
-
-// TestAndSet command
-type TestAndSetCommand struct {
-	Key        string    `json:"key"`
-	Value      string    `json:"value"`
-	ExpireTime time.Time `json:"expireTime"`
-	PrevValue  string    `json: prevValue`
-	PrevIndex  uint64    `json: prevValue`
-}
-
-// The name of the testAndSet command in the log
-func (c *TestAndSetCommand) CommandName() string {
-	return commandName("testAndSet")
-}
-
-// Set the key-value pair if the current value of the key equals to the given prevValue
-func (c *TestAndSetCommand) Apply(server *raft.Server) (interface{}, error) {
-	s, _ := server.StateMachine().(*store.Store)
-
-	e, err := s.TestAndSet(c.Key, c.PrevValue, c.PrevIndex,
-		c.Value, c.ExpireTime, server.CommitIndex(), server.Term())
-
-	if err != nil {
-		debug(err)
-		return nil, err
-	}
-
-	return e, nil
-}
-
-// Get command
-type GetCommand struct {
-	Key       string `json:"key"`
-	Recursive bool   `json:"recursive"`
-	Sorted    bool   `json:"sorted"`
-}
-
-// The name of the get command in the log
-func (c *GetCommand) CommandName() string {
-	return commandName("get")
-}
-
-// Get the value of key
-func (c *GetCommand) Apply(server *raft.Server) (interface{}, error) {
-	s, _ := server.StateMachine().(*store.Store)
-
-	e, err := s.Get(c.Key, c.Recursive, c.Sorted, server.CommitIndex(), server.Term())
-
-	if err != nil {
-		debug(err)
-		return nil, err
-	}
-
-	return e, nil
-}
-
-// Delete command
-type DeleteCommand struct {
-	Key       string `json:"key"`
-	Recursive bool   `json:"recursive"`
-}
-
-// The name of the delete command in the log
-func (c *DeleteCommand) CommandName() string {
-	return commandName("delete")
-}
-
-// Delete the key
-func (c *DeleteCommand) Apply(server *raft.Server) (interface{}, error) {
-	s, _ := server.StateMachine().(*store.Store)
-
-	e, err := s.Delete(c.Key, c.Recursive, server.CommitIndex(), server.Term())
-
-	if err != nil {
-		debug(err)
-		return nil, err
-	}
-
-	return e, nil
-}
-
-// Watch command
-type WatchCommand struct {
-	Key        string `json:"key"`
-	SinceIndex uint64 `json:"sinceIndex"`
-	Recursive  bool   `json:"recursive"`
-}
-
-// The name of the watch command in the log
-func (c *WatchCommand) CommandName() string {
-	return commandName("watch")
-}
-
-func (c *WatchCommand) Apply(server *raft.Server) (interface{}, error) {
-	s, _ := server.StateMachine().(*store.Store)
-
-	eventChan, err := s.Watch(c.Key, c.Recursive, c.SinceIndex, server.CommitIndex(), server.Term())
-
-	if err != nil {
-		return nil, err
-	}
-
-	e := <-eventChan
-
-	return e, nil
-}
-
-// JoinCommand
-type JoinCommand struct {
-	RaftVersion string `json:"raftVersion"`
-	Name        string `json:"name"`
-	RaftURL     string `json:"raftURL"`
-	EtcdURL     string `json:"etcdURL"`
-}
-
-func newJoinCommand(version, name, raftUrl, etcdUrl string) *JoinCommand {
-	return &JoinCommand{
-		RaftVersion: version,
-		Name:        name,
-		RaftURL:     raftUrl,
-		EtcdURL:     etcdUrl,
-	}
-}
-
-// The name of the join command in the log
-func (c *JoinCommand) CommandName() string {
-	return commandName("join")
-}
-
-// Join a server to the cluster
-func (c *JoinCommand) Apply(server *raft.Server) (interface{}, error) {
-	s, _ := server.StateMachine().(*store.Store)
-	r, _ := server.Context().(*raftServer)
-
-	// check if the join command is from a previous machine, who lost all its previous log.
-	e, _ := s.Get(path.Join("/_etcd/machines", c.Name), false, false, server.CommitIndex(), server.Term())
-
-	b := make([]byte, 8)
-	binary.PutUvarint(b, server.CommitIndex())
-
-	if e != nil {
-		return b, nil
-	}
-
-	// check machine number in the cluster
-	num := machineNum()
-	if num == maxClusterSize {
-		debug("Reject join request from ", c.Name)
-		return []byte{0}, etcdErr.NewError(etcdErr.EcodeNoMoreMachine, "", server.CommitIndex(), server.Term())
-	}
-
-	addNameToURL(c.Name, c.RaftVersion, c.RaftURL, c.EtcdURL)
-
-	// add peer in raft
-	err := server.AddPeer(c.Name, "")
-
-	// add machine in etcd storage
-	key := path.Join("_etcd/machines", c.Name)
-	value := fmt.Sprintf("raft=%s&etcd=%s&raftVersion=%s", c.RaftURL, c.EtcdURL, c.RaftVersion)
-	s.Create(key, value, false, false, store.Permanent, server.CommitIndex(), server.Term())
-
-	// add peer stats
-	if c.Name != r.Name() {
-		r.followersStats.Followers[c.Name] = &raftFollowerStats{}
-		r.followersStats.Followers[c.Name].Latency.Minimum = 1 << 63
-	}
-
-	return b, err
-}
-
-func (c *JoinCommand) NodeName() string {
-	return c.Name
-}
-
-// RemoveCommand
-type RemoveCommand struct {
-	Name string `json:"name"`
-}
-
-// The name of the remove command in the log
-func (c *RemoveCommand) CommandName() string {
-	return commandName("remove")
-}
-
-// Remove a server from the cluster
-func (c *RemoveCommand) Apply(server *raft.Server) (interface{}, error) {
-	s, _ := server.StateMachine().(*store.Store)
-	r, _ := server.Context().(*raftServer)
-
-	// remove machine in etcd storage
-	key := path.Join("_etcd/machines", c.Name)
-
-	_, err := s.Delete(key, false, server.CommitIndex(), server.Term())
-	// delete from stats
-	delete(r.followersStats.Followers, c.Name)
-
-	if err != nil {
-		return []byte{0}, err
-	}
-
-	// remove peer in raft
-	err = server.RemovePeer(c.Name)
-
-	if err != nil {
-		return []byte{0}, err
-	}
-
-	if c.Name == server.Name() {
-		// the removed node is this node
-
-		// if the node is not replaying the previous logs
-		// and the node has sent out a join request in this
-		// start. It is sure that this node received a new remove
-		// command and need to be removed
-		if server.CommitIndex() > r.joinIndex && r.joinIndex != 0 {
-			debugf("server [%s] is removed", server.Name())
-			os.Exit(0)
-		} else {
-			// else ignore remove
-			debugf("ignore previous remove command.")
-		}
-	}
-
-	b := make([]byte, 8)
-	binary.PutUvarint(b, server.CommitIndex())
-
-	return b, err
-}

+ 11 - 8
config.go

@@ -8,6 +8,9 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
+
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/etcd/server"
 )
 )
 
 
 //--------------------------------------
 //--------------------------------------
@@ -30,7 +33,7 @@ func getInfo(path string) *Info {
 		os.Remove(confPath)
 		os.Remove(confPath)
 		os.RemoveAll(snapshotPath)
 		os.RemoveAll(snapshotPath)
 	} else if info := readInfo(infoPath); info != nil {
 	} else if info := readInfo(infoPath); info != nil {
-		infof("Found node configuration in '%s'. Ignoring flags", infoPath)
+		log.Infof("Found node configuration in '%s'. Ignoring flags", infoPath)
 		return info
 		return info
 	}
 	}
 
 
@@ -41,10 +44,10 @@ func getInfo(path string) *Info {
 	content, _ := json.MarshalIndent(info, "", " ")
 	content, _ := json.MarshalIndent(info, "", " ")
 	content = []byte(string(content) + "\n")
 	content = []byte(string(content) + "\n")
 	if err := ioutil.WriteFile(infoPath, content, 0644); err != nil {
 	if err := ioutil.WriteFile(infoPath, content, 0644); err != nil {
-		fatalf("Unable to write info to file: %v", err)
+		log.Fatalf("Unable to write info to file: %v", err)
 	}
 	}
 
 
-	infof("Wrote node configuration to '%s'", infoPath)
+	log.Infof("Wrote node configuration to '%s'", infoPath)
 
 
 	return info
 	return info
 }
 }
@@ -57,7 +60,7 @@ func readInfo(path string) *Info {
 		if os.IsNotExist(err) {
 		if os.IsNotExist(err) {
 			return nil
 			return nil
 		}
 		}
-		fatal(err)
+		log.Fatal(err)
 	}
 	}
 	defer file.Close()
 	defer file.Close()
 
 
@@ -65,19 +68,19 @@ func readInfo(path string) *Info {
 
 
 	content, err := ioutil.ReadAll(file)
 	content, err := ioutil.ReadAll(file)
 	if err != nil {
 	if err != nil {
-		fatalf("Unable to read info: %v", err)
+		log.Fatalf("Unable to read info: %v", err)
 		return nil
 		return nil
 	}
 	}
 
 
 	if err = json.Unmarshal(content, &info); err != nil {
 	if err = json.Unmarshal(content, &info); err != nil {
-		fatalf("Unable to parse info: %v", err)
+		log.Fatalf("Unable to parse info: %v", err)
 		return nil
 		return nil
 	}
 	}
 
 
 	return info
 	return info
 }
 }
 
 
-func tlsConfigFromInfo(info TLSInfo) (t TLSConfig, ok bool) {
+func tlsConfigFromInfo(info server.TLSInfo) (t server.TLSConfig, ok bool) {
 	var keyFile, certFile, CAFile string
 	var keyFile, certFile, CAFile string
 	var tlsCert tls.Certificate
 	var tlsCert tls.Certificate
 	var err error
 	var err error
@@ -101,7 +104,7 @@ func tlsConfigFromInfo(info TLSInfo) (t TLSConfig, ok bool) {
 
 
 	tlsCert, err = tls.LoadX509KeyPair(certFile, keyFile)
 	tlsCert, err = tls.LoadX509KeyPair(certFile, keyFile)
 	if err != nil {
 	if err != nil {
-		fatal(err)
+		log.Fatal(err)
 	}
 	}
 
 
 	t.Scheme = "https"
 	t.Scheme = "https"

+ 26 - 68
etcd.go

@@ -1,15 +1,13 @@
 package main
 package main
 
 
 import (
 import (
-	"crypto/tls"
 	"flag"
 	"flag"
-	"fmt"
 	"io/ioutil"
 	"io/ioutil"
-	"net/url"
 	"os"
 	"os"
 	"strings"
 	"strings"
-	"time"
 
 
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/etcd/server"
 	"github.com/coreos/etcd/store"
 	"github.com/coreos/etcd/store"
 	"github.com/coreos/go-raft"
 	"github.com/coreos/go-raft"
 )
 )
@@ -21,7 +19,6 @@ import (
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 
 
 var (
 var (
-	verbose     bool
 	veryVerbose bool
 	veryVerbose bool
 
 
 	machines     string
 	machines     string
@@ -44,12 +41,11 @@ var (
 
 
 	cpuprofile string
 	cpuprofile string
 
 
-	cors     string
-	corsList map[string]bool
+	cors string
 )
 )
 
 
 func init() {
 func init() {
-	flag.BoolVar(&verbose, "v", false, "verbose logging")
+	flag.BoolVar(&log.Verbose, "v", false, "verbose logging")
 	flag.BoolVar(&veryVerbose, "vv", false, "very verbose logging")
 	flag.BoolVar(&veryVerbose, "vv", false, "very verbose logging")
 
 
 	flag.StringVar(&machines, "C", "", "the ip address and port of a existing machines in the cluster, sepearate by comma")
 	flag.StringVar(&machines, "C", "", "the ip address and port of a existing machines in the cluster, sepearate by comma")
@@ -87,24 +83,12 @@ func init() {
 	flag.StringVar(&cors, "cors", "", "whitelist origins for cross-origin resource sharing (e.g. '*' or 'http://localhost:8001,etc')")
 	flag.StringVar(&cors, "cors", "", "whitelist origins for cross-origin resource sharing (e.g. '*' or 'http://localhost:8001,etc')")
 }
 }
 
 
-const (
-	ElectionTimeout  = 200 * time.Millisecond
-	HeartbeatTimeout = 50 * time.Millisecond
-	RetryInterval    = 10
-)
-
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 //
 //
 // Typedefs
 // Typedefs
 //
 //
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 
 
-type TLSInfo struct {
-	CertFile string `json:"CertFile"`
-	KeyFile  string `json:"KeyFile"`
-	CAFile   string `json:"CAFile"`
-}
-
 type Info struct {
 type Info struct {
 	Name string `json:"name"`
 	Name string `json:"name"`
 
 
@@ -115,24 +99,10 @@ type Info struct {
 	RaftListenHost string `json:"raftListenHost"`
 	RaftListenHost string `json:"raftListenHost"`
 	EtcdListenHost string `json:"etcdListenHost"`
 	EtcdListenHost string `json:"etcdListenHost"`
 
 
-	RaftTLS TLSInfo `json:"raftTLS"`
-	EtcdTLS TLSInfo `json:"etcdTLS"`
-}
-
-type TLSConfig struct {
-	Scheme string
-	Server tls.Config
-	Client tls.Config
+	RaftTLS server.TLSInfo `json:"raftTLS"`
+	EtcdTLS server.TLSInfo `json:"etcdTLS"`
 }
 }
 
 
-//------------------------------------------------------------------------------
-//
-// Variables
-//
-//------------------------------------------------------------------------------
-
-var etcdStore *store.Store
-
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 //
 //
 // Functions
 // Functions
@@ -151,18 +121,16 @@ func main() {
 	}
 	}
 
 
 	if veryVerbose {
 	if veryVerbose {
-		verbose = true
+		log.Verbose = true
 		raft.SetLogLevel(raft.Debug)
 		raft.SetLogLevel(raft.Debug)
 	}
 	}
 
 
-	parseCorsFlag()
-
 	if machines != "" {
 	if machines != "" {
 		cluster = strings.Split(machines, ",")
 		cluster = strings.Split(machines, ",")
 	} else if machinesFile != "" {
 	} else if machinesFile != "" {
 		b, err := ioutil.ReadFile(machinesFile)
 		b, err := ioutil.ReadFile(machinesFile)
 		if err != nil {
 		if err != nil {
-			fatalf("Unable to read the given machines file: %s", err)
+			log.Fatalf("Unable to read the given machines file: %s", err)
 		}
 		}
 		cluster = strings.Split(string(b), ",")
 		cluster = strings.Split(string(b), ",")
 	}
 	}
@@ -170,17 +138,17 @@ func main() {
 	// Check TLS arguments
 	// Check TLS arguments
 	raftTLSConfig, ok := tlsConfigFromInfo(argInfo.RaftTLS)
 	raftTLSConfig, ok := tlsConfigFromInfo(argInfo.RaftTLS)
 	if !ok {
 	if !ok {
-		fatal("Please specify cert and key file or cert and key file and CAFile or none of the three")
+		log.Fatal("Please specify cert and key file or cert and key file and CAFile or none of the three")
 	}
 	}
 
 
 	etcdTLSConfig, ok := tlsConfigFromInfo(argInfo.EtcdTLS)
 	etcdTLSConfig, ok := tlsConfigFromInfo(argInfo.EtcdTLS)
 	if !ok {
 	if !ok {
-		fatal("Please specify cert and key file or cert and key file and CAFile or none of the three")
+		log.Fatal("Please specify cert and key file or cert and key file and CAFile or none of the three")
 	}
 	}
 
 
 	argInfo.Name = strings.TrimSpace(argInfo.Name)
 	argInfo.Name = strings.TrimSpace(argInfo.Name)
 	if argInfo.Name == "" {
 	if argInfo.Name == "" {
-		fatal("ERROR: server name required. e.g. '-n=server_name'")
+		log.Fatal("ERROR: server name required. e.g. '-n=server_name'")
 	}
 	}
 
 
 	// Check host name arguments
 	// Check host name arguments
@@ -193,39 +161,29 @@ func main() {
 
 
 	// Read server info from file or grab it from user.
 	// Read server info from file or grab it from user.
 	if err := os.MkdirAll(dirPath, 0744); err != nil {
 	if err := os.MkdirAll(dirPath, 0744); err != nil {
-		fatalf("Unable to create path: %s", err)
+		log.Fatalf("Unable to create path: %s", err)
 	}
 	}
 
 
 	info := getInfo(dirPath)
 	info := getInfo(dirPath)
 
 
 	// Create etcd key-value store
 	// Create etcd key-value store
-	etcdStore = store.New()
+	store := store.New()
 
 
-	// Create etcd and raft server
-	r := newRaftServer(info.Name, info.RaftURL, info.RaftListenHost, &raftTLSConfig, &info.RaftTLS)
-	snapConf = r.newSnapshotConf()
+	// Create a shared node registry.
+	registry := server.NewRegistry(store)
 
 
-	e = newEtcdServer(info.Name, info.EtcdURL, info.EtcdListenHost, &etcdTLSConfig, &info.EtcdTLS, r)
+	// Create peer server.
+	ps := server.NewPeerServer(info.Name, dirPath, info.RaftURL, info.RaftListenHost, &raftTLSConfig, &info.RaftTLS, registry, store)
+	ps.MaxClusterSize = maxClusterSize
+	ps.RetryTimes = retryTimes
 
 
-	r.ListenAndServe()
-	e.ListenAndServe()
+	s := server.New(info.Name, info.EtcdURL, info.EtcdListenHost, &etcdTLSConfig, &info.EtcdTLS, ps, registry, store)
+	if err := s.AllowOrigins(cors); err != nil {
+		panic(err)
+	}
 
 
-}
+	ps.SetServer(s)
 
 
-// parseCorsFlag gathers up the cors whitelist and puts it into the corsList.
-func parseCorsFlag() {
-	if cors != "" {
-		corsList = make(map[string]bool)
-		list := strings.Split(cors, ",")
-		for _, v := range list {
-			fmt.Println(v)
-			if v != "*" {
-				_, err := url.Parse(v)
-				if err != nil {
-					panic(fmt.Sprintf("bad cors url: %s", err))
-				}
-			}
-			corsList[v] = true
-		}
-	}
+	ps.ListenAndServe(snapshot, cluster)
+	s.ListenAndServe()
 }
 }

+ 0 - 250
etcd_handler_v1.go

@@ -1,250 +0,0 @@
-package main
-
-import (
-	"encoding/json"
-	"net/http"
-	"strconv"
-
-	etcdErr "github.com/coreos/etcd/error"
-	"github.com/coreos/etcd/store"
-	"github.com/coreos/go-raft"
-)
-
-//-------------------------------------------------------------------
-// Handlers to handle etcd-store related request via etcd url
-//-------------------------------------------------------------------
-// Multiplex GET/POST/DELETE request to corresponding handlers
-func (e *etcdServer) MultiplexerV1(w http.ResponseWriter, req *http.Request) error {
-
-	switch req.Method {
-	case "GET":
-		return e.GetHttpHandlerV1(w, req)
-	case "POST":
-		return e.SetHttpHandlerV1(w, req)
-	case "PUT":
-		return e.SetHttpHandlerV1(w, req)
-	case "DELETE":
-		return e.DeleteHttpHandlerV1(w, req)
-	default:
-		w.WriteHeader(http.StatusMethodNotAllowed)
-		return nil
-	}
-}
-
-//--------------------------------------
-// State sensitive handlers
-// Set/Delete will dispatch to leader
-//--------------------------------------
-
-// Set Command Handler
-func (e *etcdServer) SetHttpHandlerV1(w http.ResponseWriter, req *http.Request) error {
-	key := req.URL.Path[len("/v1/keys/"):]
-
-	debugf("[recv] POST %v/v1/keys/%s [%s]", e.url, key, req.RemoteAddr)
-
-	req.ParseForm()
-
-	value := req.Form.Get("value")
-
-	if len(value) == 0 {
-		return etcdErr.NewError(200, "Set", store.UndefIndex, store.UndefTerm)
-	}
-
-	strDuration := req.Form.Get("ttl")
-
-	expireTime, err := durationToExpireTime(strDuration)
-
-	if err != nil {
-		return etcdErr.NewError(202, "Set", store.UndefIndex, store.UndefTerm)
-	}
-
-	if prevValueArr, ok := req.Form["prevValue"]; ok && len(prevValueArr) > 0 {
-		command := &TestAndSetCommand{
-			Key:        key,
-			Value:      value,
-			PrevValue:  prevValueArr[0],
-			ExpireTime: expireTime,
-		}
-
-		return dispatchEtcdCommandV1(command, w, req)
-
-	} else {
-		command := &CreateCommand{
-			Key:        key,
-			Value:      value,
-			ExpireTime: expireTime,
-			Force:      true,
-		}
-
-		return dispatchEtcdCommandV1(command, w, req)
-	}
-}
-
-// Delete Handler
-func (e *etcdServer) DeleteHttpHandlerV1(w http.ResponseWriter, req *http.Request) error {
-	key := req.URL.Path[len("/v1/keys/"):]
-
-	debugf("[recv] DELETE %v/v1/keys/%s [%s]", e.url, key, req.RemoteAddr)
-
-	command := &DeleteCommand{
-		Key: key,
-	}
-
-	return dispatchEtcdCommandV1(command, w, req)
-}
-
-//--------------------------------------
-// State non-sensitive handlers
-// will not dispatch to leader
-// TODO: add sensitive version for these
-// command?
-//--------------------------------------
-
-// Get Handler
-func (e *etcdServer) GetHttpHandlerV1(w http.ResponseWriter, req *http.Request) error {
-	key := req.URL.Path[len("/v1/keys/"):]
-
-	r := e.raftServer
-	debugf("[recv] GET %s/v1/keys/%s [%s]", e.url, key, req.RemoteAddr)
-
-	command := &GetCommand{
-		Key: key,
-	}
-
-	if event, err := command.Apply(r.Server); err != nil {
-		return err
-	} else {
-		event, _ := event.(*store.Event)
-
-		response := eventToResponse(event)
-		bytes, _ := json.Marshal(response)
-
-		w.WriteHeader(http.StatusOK)
-
-		w.Write(bytes)
-
-		return nil
-	}
-
-}
-
-// Watch handler
-func (e *etcdServer) WatchHttpHandlerV1(w http.ResponseWriter, req *http.Request) error {
-	key := req.URL.Path[len("/v1/watch/"):]
-
-	command := &WatchCommand{
-		Key: key,
-	}
-	r := e.raftServer
-	if req.Method == "GET" {
-		debugf("[recv] GET %s/watch/%s [%s]", e.url, key, req.RemoteAddr)
-		command.SinceIndex = 0
-
-	} else if req.Method == "POST" {
-		// watch from a specific index
-
-		debugf("[recv] POST %s/watch/%s [%s]", e.url, key, req.RemoteAddr)
-		content := req.FormValue("index")
-
-		sinceIndex, err := strconv.ParseUint(string(content), 10, 64)
-		if err != nil {
-			return etcdErr.NewError(203, "Watch From Index", store.UndefIndex, store.UndefTerm)
-		}
-		command.SinceIndex = sinceIndex
-
-	} else {
-		w.WriteHeader(http.StatusMethodNotAllowed)
-		return nil
-	}
-
-	if event, err := command.Apply(r.Server); err != nil {
-		return etcdErr.NewError(500, key, store.UndefIndex, store.UndefTerm)
-	} else {
-		event, _ := event.(*store.Event)
-
-		response := eventToResponse(event)
-		bytes, _ := json.Marshal(response)
-
-		w.WriteHeader(http.StatusOK)
-
-		w.Write(bytes)
-		return nil
-	}
-
-}
-
-// Dispatch the command to leader
-func dispatchEtcdCommandV1(c Command, w http.ResponseWriter, req *http.Request) error {
-	return dispatchV1(c, w, req, nameToEtcdURL)
-}
-
-func dispatchV1(c Command, w http.ResponseWriter, req *http.Request, toURL func(name string) (string, bool)) error {
-	r := e.raftServer
-	if r.State() == raft.Leader {
-		if event, err := r.Do(c); err != nil {
-			return err
-		} else {
-			if event == nil {
-				return etcdErr.NewError(300, "Empty result from raft", store.UndefIndex, store.UndefTerm)
-			}
-
-			event, _ := event.(*store.Event)
-
-			response := eventToResponse(event)
-			bytes, _ := json.Marshal(response)
-
-			w.WriteHeader(http.StatusOK)
-			w.Write(bytes)
-			return nil
-
-		}
-
-	} else {
-		leader := r.Leader()
-		// current no leader
-		if leader == "" {
-			return etcdErr.NewError(300, "", store.UndefIndex, store.UndefTerm)
-		}
-		url, _ := toURL(leader)
-
-		redirect(url, w, req)
-
-		return nil
-	}
-}
-
-func eventToResponse(event *store.Event) interface{} {
-	if !event.Dir {
-		response := &store.Response{
-			Action:     event.Action,
-			Key:        event.Key,
-			Value:      event.Value,
-			PrevValue:  event.PrevValue,
-			Index:      event.Index,
-			TTL:        event.TTL,
-			Expiration: event.Expiration,
-		}
-
-		if response.Action == store.Create || response.Action == store.Update {
-			response.Action = "set"
-			if response.PrevValue == "" {
-				response.NewKey = true
-			}
-		}
-
-		return response
-	} else {
-		responses := make([]*store.Response, len(event.KVPairs))
-
-		for i, kv := range event.KVPairs {
-			responses[i] = &store.Response{
-				Action: event.Action,
-				Key:    kv.Key,
-				Value:  kv.Value,
-				Dir:    kv.Dir,
-				Index:  event.Index,
-			}
-		}
-		return responses
-	}
-}

+ 0 - 364
etcd_handlers.go

@@ -1,364 +0,0 @@
-package main
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"strconv"
-	"strings"
-
-	etcdErr "github.com/coreos/etcd/error"
-	"github.com/coreos/etcd/store"
-	"github.com/coreos/go-raft"
-)
-
-//-------------------------------------------------------------------
-// Handlers to handle etcd-store related request via etcd url
-//-------------------------------------------------------------------
-
-func NewEtcdMuxer() *http.ServeMux {
-	// external commands
-	etcdMux := http.NewServeMux()
-	etcdMux.Handle("/"+version+"/keys/", errorHandler(e.Multiplexer))
-	etcdMux.Handle("/"+version+"/leader", errorHandler(e.LeaderHttpHandler))
-	etcdMux.Handle("/"+version+"/machines", errorHandler(e.MachinesHttpHandler))
-	etcdMux.Handle("/"+version+"/stats/", errorHandler(e.StatsHttpHandler))
-	etcdMux.Handle("/version", errorHandler(e.VersionHttpHandler))
-	etcdMux.HandleFunc("/test/", TestHttpHandler)
-
-	// backward support
-	etcdMux.Handle("/v1/keys/", errorHandler(e.MultiplexerV1))
-	etcdMux.Handle("/v1/leader", errorHandler(e.LeaderHttpHandler))
-	etcdMux.Handle("/v1/machines", errorHandler(e.MachinesHttpHandler))
-	etcdMux.Handle("/v1/stats/", errorHandler(e.StatsHttpHandler))
-
-	return etcdMux
-}
-
-type errorHandler func(http.ResponseWriter, *http.Request) error
-
-// addCorsHeader parses the request Origin header and loops through the user
-// provided allowed origins and sets the Access-Control-Allow-Origin header if
-// there is a match.
-func addCorsHeader(w http.ResponseWriter, r *http.Request) {
-	val, ok := corsList["*"]
-	if val && ok {
-		w.Header().Add("Access-Control-Allow-Origin", "*")
-		return
-	}
-
-	requestOrigin := r.Header.Get("Origin")
-	val, ok = corsList[requestOrigin]
-	if val && ok {
-		w.Header().Add("Access-Control-Allow-Origin", requestOrigin)
-		return
-	}
-}
-
-func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	addCorsHeader(w, r)
-	if e := fn(w, r); e != nil {
-		if etcdErr, ok := e.(*etcdErr.Error); ok {
-			debug("Return error: ", (*etcdErr).Error())
-			etcdErr.Write(w)
-		} else {
-			http.Error(w, e.Error(), http.StatusInternalServerError)
-		}
-	}
-}
-
-// Multiplex GET/POST/DELETE request to corresponding handlers
-func (e *etcdServer) Multiplexer(w http.ResponseWriter, req *http.Request) error {
-
-	switch req.Method {
-	case "GET":
-		return e.GetHttpHandler(w, req)
-	case "POST":
-		return e.CreateHttpHandler(w, req)
-	case "PUT":
-		return e.UpdateHttpHandler(w, req)
-	case "DELETE":
-		return e.DeleteHttpHandler(w, req)
-	default:
-		w.WriteHeader(http.StatusMethodNotAllowed)
-		return nil
-	}
-
-	return nil
-}
-
-//--------------------------------------
-// State sensitive handlers
-// Set/Delete will dispatch to leader
-//--------------------------------------
-
-func (e *etcdServer) CreateHttpHandler(w http.ResponseWriter, req *http.Request) error {
-	key := getNodePath(req.URL.Path)
-
-	debugf("recv.post[%v] [%v%v]\n", req.RemoteAddr, req.Host, req.URL)
-
-	value := req.FormValue("value")
-
-	expireTime, err := durationToExpireTime(req.FormValue("ttl"))
-
-	if err != nil {
-		return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Create", store.UndefIndex, store.UndefTerm)
-	}
-
-	command := &CreateCommand{
-		Key:        key,
-		Value:      value,
-		ExpireTime: expireTime,
-	}
-
-	if req.FormValue("incremental") == "true" {
-		command.IncrementalSuffix = true
-	}
-
-	return e.dispatchEtcdCommand(command, w, req)
-
-}
-
-func (e *etcdServer) UpdateHttpHandler(w http.ResponseWriter, req *http.Request) error {
-	key := getNodePath(req.URL.Path)
-
-	debugf("recv.put[%v] [%v%v]\n", req.RemoteAddr, req.Host, req.URL)
-
-	req.ParseForm()
-
-	value := req.Form.Get("value")
-
-	expireTime, err := durationToExpireTime(req.Form.Get("ttl"))
-
-	if err != nil {
-		return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Update", store.UndefIndex, store.UndefTerm)
-	}
-
-	// update should give at least one option
-	if value == "" && expireTime.Sub(store.Permanent) == 0 {
-		return etcdErr.NewError(etcdErr.EcodeValueOrTTLRequired, "Update", store.UndefIndex, store.UndefTerm)
-	}
-
-	prevValue, valueOk := req.Form["prevValue"]
-
-	prevIndexStr, indexOk := req.Form["prevIndex"]
-
-	if !valueOk && !indexOk { // update without test
-		command := &UpdateCommand{
-			Key:        key,
-			Value:      value,
-			ExpireTime: expireTime,
-		}
-
-		return e.dispatchEtcdCommand(command, w, req)
-
-	} else { // update with test
-		var prevIndex uint64
-
-		if indexOk {
-			prevIndex, err = strconv.ParseUint(prevIndexStr[0], 10, 64)
-
-			// bad previous index
-			if err != nil {
-				return etcdErr.NewError(etcdErr.EcodeIndexNaN, "Update", store.UndefIndex, store.UndefTerm)
-			}
-		} else {
-			prevIndex = 0
-		}
-
-		command := &TestAndSetCommand{
-			Key:       key,
-			Value:     value,
-			PrevValue: prevValue[0],
-			PrevIndex: prevIndex,
-		}
-
-		return e.dispatchEtcdCommand(command, w, req)
-	}
-
-}
-
-// Delete Handler
-func (e *etcdServer) DeleteHttpHandler(w http.ResponseWriter, req *http.Request) error {
-	key := getNodePath(req.URL.Path)
-
-	debugf("recv.delete[%v] [%v%v]\n", req.RemoteAddr, req.Host, req.URL)
-
-	command := &DeleteCommand{
-		Key: key,
-	}
-
-	if req.FormValue("recursive") == "true" {
-		command.Recursive = true
-	}
-
-	return e.dispatchEtcdCommand(command, w, req)
-}
-
-// Dispatch the command to leader
-func (e *etcdServer) dispatchEtcdCommand(c Command, w http.ResponseWriter, req *http.Request) error {
-	return e.raftServer.dispatch(c, w, req, nameToEtcdURL)
-}
-
-//--------------------------------------
-// State non-sensitive handlers
-// command with consistent option will
-// still dispatch to the leader
-//--------------------------------------
-
-// Handler to return the current leader's raft address
-func (e *etcdServer) LeaderHttpHandler(w http.ResponseWriter, req *http.Request) error {
-	r := e.raftServer
-
-	leader := r.Leader()
-
-	if leader != "" {
-		w.WriteHeader(http.StatusOK)
-		raftURL, _ := nameToRaftURL(leader)
-		w.Write([]byte(raftURL))
-
-		return nil
-	} else {
-		return etcdErr.NewError(etcdErr.EcodeLeaderElect, "", store.UndefIndex, store.UndefTerm)
-	}
-}
-
-// Handler to return all the known machines in the current cluster
-func (e *etcdServer) MachinesHttpHandler(w http.ResponseWriter, req *http.Request) error {
-	machines := e.raftServer.getMachines(nameToEtcdURL)
-
-	w.WriteHeader(http.StatusOK)
-	w.Write([]byte(strings.Join(machines, ", ")))
-
-	return nil
-}
-
-// Handler to return the current version of etcd
-func (e *etcdServer) VersionHttpHandler(w http.ResponseWriter, req *http.Request) error {
-	w.WriteHeader(http.StatusOK)
-	fmt.Fprintf(w, "etcd %s", releaseVersion)
-
-	return nil
-}
-
-// Handler to return the basic stats of etcd
-func (e *etcdServer) StatsHttpHandler(w http.ResponseWriter, req *http.Request) error {
-	option := req.URL.Path[len("/v1/stats/"):]
-	w.WriteHeader(http.StatusOK)
-
-	r := e.raftServer
-
-	switch option {
-	case "self":
-		w.Write(r.Stats())
-	case "leader":
-		if r.State() == raft.Leader {
-			w.Write(r.PeerStats())
-		} else {
-			leader := r.Leader()
-			// current no leader
-			if leader == "" {
-				return etcdErr.NewError(300, "", store.UndefIndex, store.UndefTerm)
-			}
-			hostname, _ := nameToEtcdURL(leader)
-			redirect(hostname, w, req)
-		}
-	case "store":
-		w.Write(etcdStore.JsonStats())
-	}
-
-	return nil
-}
-
-func (e *etcdServer) GetHttpHandler(w http.ResponseWriter, req *http.Request) error {
-	var err error
-	var event interface{}
-
-	r := e.raftServer
-
-	debugf("recv.get[%v] [%v%v]\n", req.RemoteAddr, req.Host, req.URL)
-
-	if req.FormValue("consistent") == "true" && r.State() != raft.Leader {
-		// help client to redirect the request to the current leader
-		leader := r.Leader()
-		hostname, _ := nameToEtcdURL(leader)
-		redirect(hostname, w, req)
-		return nil
-	}
-
-	key := getNodePath(req.URL.Path)
-
-	recursive := req.FormValue("recursive")
-
-	if req.FormValue("wait") == "true" { // watch
-		command := &WatchCommand{
-			Key: key,
-		}
-
-		if recursive == "true" {
-			command.Recursive = true
-		}
-
-		indexStr := req.FormValue("wait_index")
-		if indexStr != "" {
-			sinceIndex, err := strconv.ParseUint(indexStr, 10, 64)
-
-			if err != nil {
-				return etcdErr.NewError(etcdErr.EcodeIndexNaN, "Watch From Index", store.UndefIndex, store.UndefTerm)
-			}
-
-			command.SinceIndex = sinceIndex
-		}
-
-		event, err = command.Apply(r.Server)
-
-	} else { //get
-
-		command := &GetCommand{
-			Key: key,
-		}
-
-		sorted := req.FormValue("sorted")
-		if sorted == "true" {
-			command.Sorted = true
-		}
-
-		if recursive == "true" {
-			command.Recursive = true
-		}
-
-		event, err = command.Apply(r.Server)
-	}
-
-	if err != nil {
-		return err
-
-	} else {
-		event, _ := event.(*store.Event)
-		bytes, _ := json.Marshal(event)
-
-		w.Header().Add("X-Etcd-Index", fmt.Sprint(event.Index))
-		w.Header().Add("X-Etcd-Term", fmt.Sprint(event.Term))
-		w.WriteHeader(http.StatusOK)
-
-		w.Write(bytes)
-
-		return nil
-	}
-
-}
-
-// TestHandler
-func TestHttpHandler(w http.ResponseWriter, req *http.Request) {
-	testType := req.URL.Path[len("/test/"):]
-
-	if testType == "speed" {
-		directSet()
-		w.WriteHeader(http.StatusOK)
-		w.Write([]byte("speed test success"))
-
-		return
-	}
-
-	w.WriteHeader(http.StatusBadRequest)
-}

+ 0 - 44
etcd_server.go

@@ -1,44 +0,0 @@
-package main
-
-import (
-	"net/http"
-)
-
-type etcdServer struct {
-	http.Server
-	raftServer *raftServer
-	name       string
-	url        string
-	tlsConf    *TLSConfig
-	tlsInfo    *TLSInfo
-}
-
-var e *etcdServer
-
-func newEtcdServer(name string, urlStr string, listenHost string, tlsConf *TLSConfig, tlsInfo *TLSInfo, raftServer *raftServer) *etcdServer {
-	e = &etcdServer{
-		Server: http.Server{
-			TLSConfig: &tlsConf.Server,
-			Addr:      listenHost,
-		},
-		name:       name,
-		url:        urlStr,
-		tlsConf:    tlsConf,
-		tlsInfo:    tlsInfo,
-		raftServer: raftServer,
-	}
-	e.Handler = NewEtcdMuxer()
-	return e
-}
-
-// Start to listen and response etcd client command
-func (e *etcdServer) ListenAndServe() {
-
-	infof("etcd server [name %s, listen on %s, advertised url %s]", e.name, e.Server.Addr, e.url)
-
-	if e.tlsConf.Scheme == "http" {
-		fatal(e.Server.ListenAndServe())
-	} else {
-		fatal(e.Server.ListenAndServeTLS(e.tlsInfo.CertFile, e.tlsInfo.KeyFile))
-	}
-}

+ 12 - 14
etcd_test.go

@@ -12,6 +12,7 @@ import (
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
+	"github.com/coreos/etcd/server"
 	"github.com/coreos/etcd/test"
 	"github.com/coreos/etcd/test"
 	"github.com/coreos/go-etcd/etcd"
 	"github.com/coreos/go-etcd/etcd"
 )
 )
@@ -39,7 +40,7 @@ func TestSingleNode(t *testing.T) {
 
 
 	if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 {
 	if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 {
 		if err != nil {
 		if err != nil {
-			t.Fatal(err)
+			t.Fatal("Set 1: ", err)
 		}
 		}
 
 
 		t.Fatalf("Set 1 failed with %s %s %v", result.Key, result.Value, result.TTL)
 		t.Fatalf("Set 1 failed with %s %s %v", result.Key, result.Value, result.TTL)
@@ -51,7 +52,7 @@ func TestSingleNode(t *testing.T) {
 
 
 	if err != nil || result.Key != "/foo" || result.Value != "bar" || result.PrevValue != "bar" || result.TTL != 100 {
 	if err != nil || result.Key != "/foo" || result.Value != "bar" || result.PrevValue != "bar" || result.TTL != 100 {
 		if err != nil {
 		if err != nil {
-			t.Fatal(err)
+			t.Fatal("Set 2: ", err)
 		}
 		}
 		t.Fatalf("Set 2 failed with %s %s %v", result.Key, result.Value, result.TTL)
 		t.Fatalf("Set 2 failed with %s %s %v", result.Key, result.Value, result.TTL)
 	}
 	}
@@ -105,7 +106,7 @@ func TestInternalVersionFail(t *testing.T) {
 
 
 	procAttr := new(os.ProcAttr)
 	procAttr := new(os.ProcAttr)
 	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
 	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
-	args := []string{"etcd", "-n=node1", "-f", "-d=/tmp/node1", "-vv", "-C=" + fakeURL.Host}
+	args := []string{"etcd", "-n=node1", "-f", "-d=/tmp/node1", "-C=" + fakeURL.Host}
 
 
 	process, err := os.StartProcess("etcd", args, procAttr)
 	process, err := os.StartProcess("etcd", args, procAttr)
 	if err != nil {
 	if err != nil {
@@ -248,6 +249,7 @@ func TestMultiNodeKillAllAndRecovery(t *testing.T) {
 
 
 	clusterSize := 5
 	clusterSize := 5
 	argGroup, etcds, err := test.CreateCluster(clusterSize, procAttr, false)
 	argGroup, etcds, err := test.CreateCluster(clusterSize, procAttr, false)
+	defer test.DestroyCluster(etcds)
 
 
 	if err != nil {
 	if err != nil {
 		t.Fatal("cannot create cluster")
 		t.Fatal("cannot create cluster")
@@ -293,15 +295,12 @@ func TestMultiNodeKillAllAndRecovery(t *testing.T) {
 	result, err := c.Set("foo", "bar", 0)
 	result, err := c.Set("foo", "bar", 0)
 
 
 	if err != nil {
 	if err != nil {
-		panic(err)
+		t.Fatalf("Recovery error: %s", err)
 	}
 	}
 
 
 	if result.Index != 18 {
 	if result.Index != 18 {
 		t.Fatalf("recovery failed! [%d/18]", result.Index)
 		t.Fatalf("recovery failed! [%d/18]", result.Index)
 	}
 	}
-
-	// kill all
-	test.DestroyCluster(etcds)
 }
 }
 
 
 // Create a five nodes
 // Create a five nodes
@@ -398,8 +397,8 @@ func TestKillLeader(t *testing.T) {
 		totalTime += take
 		totalTime += take
 		avgTime := totalTime / (time.Duration)(i+1)
 		avgTime := totalTime / (time.Duration)(i+1)
 
 
-		fmt.Println("Leader election time is ", take, "with election timeout", ElectionTimeout)
-		fmt.Println("Leader election time average is", avgTime, "with election timeout", ElectionTimeout)
+		fmt.Println("Leader election time is ", take, "with election timeout", server.ElectionTimeout)
+		fmt.Println("Leader election time average is", avgTime, "with election timeout", server.ElectionTimeout)
 		etcds[num], err = os.StartProcess("etcd", argGroup[num], procAttr)
 		etcds[num], err = os.StartProcess("etcd", argGroup[num], procAttr)
 	}
 	}
 	stop <- true
 	stop <- true
@@ -456,7 +455,7 @@ func TestKillRandom(t *testing.T) {
 			etcds[num].Wait()
 			etcds[num].Wait()
 		}
 		}
 
 
-		time.Sleep(ElectionTimeout)
+		time.Sleep(server.ElectionTimeout)
 
 
 		<-leaderChan
 		<-leaderChan
 
 
@@ -478,6 +477,7 @@ func TestRemoveNode(t *testing.T) {
 
 
 	clusterSize := 3
 	clusterSize := 3
 	argGroup, etcds, _ := test.CreateCluster(clusterSize, procAttr, false)
 	argGroup, etcds, _ := test.CreateCluster(clusterSize, procAttr, false)
+	defer test.DestroyCluster(etcds)
 
 
 	time.Sleep(time.Second)
 	time.Sleep(time.Second)
 
 
@@ -525,7 +525,7 @@ func TestRemoveNode(t *testing.T) {
 			}
 			}
 
 
 			if len(resp) != 3 {
 			if len(resp) != 3 {
-				t.Fatal("add machine fails")
+				t.Fatalf("add machine fails #1 (%d != 3)", len(resp))
 			}
 			}
 		}
 		}
 
 
@@ -567,12 +567,10 @@ func TestRemoveNode(t *testing.T) {
 			}
 			}
 
 
 			if len(resp) != 3 {
 			if len(resp) != 3 {
-				t.Fatal("add machine fails")
+				t.Fatalf("add machine fails #2 (%d != 3)", len(resp))
 			}
 			}
 		}
 		}
 	}
 	}
-	test.DestroyCluster(etcds)
-
 }
 }
 
 
 func templateBenchmarkEtcdDirectCall(b *testing.B, tls bool) {
 func templateBenchmarkEtcdDirectCall(b *testing.B, tls bool) {

+ 44 - 0
log/log.go

@@ -0,0 +1,44 @@
+package log
+
+import (
+	golog "github.com/coreos/go-log/log"
+	"os"
+)
+
+// The Verbose flag turns on verbose logging.
+var Verbose bool = false
+
+var logger *golog.Logger = golog.New("etcd", false,
+	golog.CombinedSink(os.Stdout, "[%s] %s %-9s | %s\n", []string{"prefix", "time", "priority", "message"}))
+
+func Infof(format string, v ...interface{}) {
+	logger.Infof(format, v...)
+}
+
+func Debugf(format string, v ...interface{}) {
+	if Verbose {
+		logger.Debugf(format, v...)
+	}
+}
+
+func Debug(v ...interface{}) {
+	if Verbose {
+		logger.Debug(v...)
+	}
+}
+
+func Warnf(format string, v ...interface{}) {
+	logger.Warningf(format, v...)
+}
+
+func Warn(v ...interface{}) {
+	logger.Warning(v...)
+}
+
+func Fatalf(format string, v ...interface{}) {
+	logger.Fatalf(format, v...)
+}
+
+func Fatal(v ...interface{}) {
+	logger.Fatalln(v...)
+}

+ 0 - 45
machines.go

@@ -1,45 +0,0 @@
-package main
-
-// machineNum returns the number of machines in the cluster
-func machineNum() int {
-	e, err := etcdStore.Get("/_etcd/machines", false, false, 0, 0)
-
-	if err != nil {
-		return 0
-	}
-
-	return len(e.KVPairs)
-}
-
-// getMachines gets the current machines in the cluster
-func (r *raftServer) getMachines(toURL func(string) (string, bool)) []string {
-	peers := r.Peers()
-
-	machines := make([]string, len(peers)+1)
-
-	leader, ok := toURL(r.Leader())
-	self, _ := toURL(r.Name())
-	i := 1
-
-	if ok {
-		machines[0] = leader
-		if leader != self {
-			machines[1] = self
-			i = 2
-		}
-	} else {
-		machines[0] = self
-	}
-
-	// Add all peers to the slice
-	for peerName, _ := range peers {
-		if machine, ok := toURL(peerName); ok {
-			// do not add leader twice
-			if machine != leader {
-				machines[i] = machine
-				i++
-			}
-		}
-	}
-	return machines
-}

+ 0 - 74
name_url_map.go

@@ -1,74 +0,0 @@
-package main
-
-import (
-	"net/url"
-	"path"
-)
-
-// we map node name to url
-type nodeInfo struct {
-	raftVersion string
-	raftURL     string
-	etcdURL     string
-}
-
-var namesMap = make(map[string]*nodeInfo)
-
-// nameToEtcdURL maps node name to its etcd http address
-func nameToEtcdURL(name string) (string, bool) {
-
-	if info, ok := namesMap[name]; ok {
-		// first try to read from the map
-		return info.etcdURL, true
-	}
-
-	// if fails, try to recover from etcd storage
-	return readURL(name, "etcd")
-
-}
-
-// nameToRaftURL maps node name to its raft http address
-func nameToRaftURL(name string) (string, bool) {
-	if info, ok := namesMap[name]; ok {
-		// first try to read from the map
-		return info.raftURL, true
-
-	}
-
-	// if fails, try to recover from etcd storage
-	return readURL(name, "raft")
-}
-
-// addNameToURL add a name that maps to raftURL and etcdURL
-func addNameToURL(name string, version string, raftURL string, etcdURL string) {
-	namesMap[name] = &nodeInfo{
-		raftVersion: raftVersion,
-		raftURL:     raftURL,
-		etcdURL:     etcdURL,
-	}
-}
-
-func readURL(nodeName string, urlName string) (string, bool) {
-	if nodeName == "" {
-		return "", false
-	}
-
-	// convert nodeName to url from etcd storage
-	key := path.Join("/_etcd/machines", nodeName)
-
-	e, err := etcdStore.Get(key, false, false, 0, 0)
-
-	if err != nil {
-		return "", false
-	}
-
-	m, err := url.ParseQuery(e.Value)
-
-	if err != nil {
-		panic("Failed to parse machines entry")
-	}
-
-	url := m[urlName][0]
-
-	return url, true
-}

+ 0 - 147
raft_handlers.go

@@ -1,147 +0,0 @@
-package main
-
-import (
-	"encoding/json"
-	"net/http"
-
-	"github.com/coreos/go-raft"
-)
-
-//-------------------------------------------------------------
-// Handlers to handle raft related request via raft server port
-//-------------------------------------------------------------
-
-// Get all the current logs
-func (r *raftServer) GetLogHttpHandler(w http.ResponseWriter, req *http.Request) {
-	debugf("[recv] GET %s/log", r.url)
-	w.Header().Set("Content-Type", "application/json")
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(r.LogEntries())
-}
-
-// Response to vote request
-func (r *raftServer) VoteHttpHandler(w http.ResponseWriter, req *http.Request) {
-	rvreq := &raft.RequestVoteRequest{}
-	err := decodeJsonRequest(req, rvreq)
-	if err == nil {
-		debugf("[recv] POST %s/vote [%s]", r.url, rvreq.CandidateName)
-		if resp := r.RequestVote(rvreq); resp != nil {
-			w.WriteHeader(http.StatusOK)
-			json.NewEncoder(w).Encode(resp)
-			return
-		}
-	}
-	warnf("[vote] ERROR: %v", err)
-	w.WriteHeader(http.StatusInternalServerError)
-}
-
-// Response to append entries request
-func (r *raftServer) AppendEntriesHttpHandler(w http.ResponseWriter, req *http.Request) {
-	aereq := &raft.AppendEntriesRequest{}
-	err := decodeJsonRequest(req, aereq)
-
-	if err == nil {
-		debugf("[recv] POST %s/log/append [%d]", r.url, len(aereq.Entries))
-
-		r.serverStats.RecvAppendReq(aereq.LeaderName, int(req.ContentLength))
-
-		if resp := r.AppendEntries(aereq); resp != nil {
-			w.WriteHeader(http.StatusOK)
-			json.NewEncoder(w).Encode(resp)
-			if !resp.Success {
-				debugf("[Append Entry] Step back")
-			}
-			return
-		}
-	}
-	warnf("[Append Entry] ERROR: %v", err)
-	w.WriteHeader(http.StatusInternalServerError)
-}
-
-// Response to recover from snapshot request
-func (r *raftServer) SnapshotHttpHandler(w http.ResponseWriter, req *http.Request) {
-	aereq := &raft.SnapshotRequest{}
-	err := decodeJsonRequest(req, aereq)
-	if err == nil {
-		debugf("[recv] POST %s/snapshot/ ", r.url)
-		if resp := r.RequestSnapshot(aereq); resp != nil {
-			w.WriteHeader(http.StatusOK)
-			json.NewEncoder(w).Encode(resp)
-			return
-		}
-	}
-	warnf("[Snapshot] ERROR: %v", err)
-	w.WriteHeader(http.StatusInternalServerError)
-}
-
-// Response to recover from snapshot request
-func (r *raftServer) SnapshotRecoveryHttpHandler(w http.ResponseWriter, req *http.Request) {
-	aereq := &raft.SnapshotRecoveryRequest{}
-	err := decodeJsonRequest(req, aereq)
-	if err == nil {
-		debugf("[recv] POST %s/snapshotRecovery/ ", r.url)
-		if resp := r.SnapshotRecoveryRequest(aereq); resp != nil {
-			w.WriteHeader(http.StatusOK)
-			json.NewEncoder(w).Encode(resp)
-			return
-		}
-	}
-	warnf("[Snapshot] ERROR: %v", err)
-	w.WriteHeader(http.StatusInternalServerError)
-}
-
-// Get the port that listening for etcd connecting of the server
-func (r *raftServer) EtcdURLHttpHandler(w http.ResponseWriter, req *http.Request) {
-	debugf("[recv] Get %s/etcdURL/ ", r.url)
-	w.WriteHeader(http.StatusOK)
-	w.Write([]byte(argInfo.EtcdURL))
-}
-
-// Response to the join request
-func (r *raftServer) JoinHttpHandler(w http.ResponseWriter, req *http.Request) error {
-
-	command := &JoinCommand{}
-
-	if err := decodeJsonRequest(req, command); err == nil {
-		debugf("Receive Join Request from %s", command.Name)
-		return r.dispatchRaftCommand(command, w, req)
-	} else {
-		w.WriteHeader(http.StatusInternalServerError)
-		return nil
-	}
-}
-
-// Response to remove request
-func (r *raftServer) RemoveHttpHandler(w http.ResponseWriter, req *http.Request) {
-	if req.Method != "DELETE" {
-		w.WriteHeader(http.StatusMethodNotAllowed)
-		return
-	}
-
-	nodeName := req.URL.Path[len("/remove/"):]
-	command := &RemoveCommand{
-		Name: nodeName,
-	}
-
-	debugf("[recv] Remove Request [%s]", command.Name)
-
-	r.dispatchRaftCommand(command, w, req)
-}
-
-// Response to the name request
-func (r *raftServer) NameHttpHandler(w http.ResponseWriter, req *http.Request) {
-	debugf("[recv] Get %s/name/ ", r.url)
-	w.WriteHeader(http.StatusOK)
-	w.Write([]byte(r.name))
-}
-
-// Response to the name request
-func (r *raftServer) RaftVersionHttpHandler(w http.ResponseWriter, req *http.Request) {
-	debugf("[recv] Get %s/version/ ", r.url)
-	w.WriteHeader(http.StatusOK)
-	w.Write([]byte(r.version))
-}
-
-func (r *raftServer) dispatchRaftCommand(c Command, w http.ResponseWriter, req *http.Request) error {
-	return r.dispatch(c, w, req, nameToRaftURL)
-}

+ 0 - 329
raft_server.go

@@ -1,329 +0,0 @@
-package main
-
-import (
-	"bytes"
-	"crypto/tls"
-	"encoding/binary"
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"net/url"
-	"time"
-
-	etcdErr "github.com/coreos/etcd/error"
-	"github.com/coreos/go-raft"
-)
-
-type raftServer struct {
-	*raft.Server
-	version        string
-	joinIndex      uint64
-	name           string
-	url            string
-	listenHost     string
-	tlsConf        *TLSConfig
-	tlsInfo        *TLSInfo
-	followersStats *raftFollowersStats
-	serverStats    *raftServerStats
-}
-
-//var r *raftServer
-
-func newRaftServer(name string, url string, listenHost string, tlsConf *TLSConfig, tlsInfo *TLSInfo) *raftServer {
-
-	raftWrapper := &raftServer{
-		version:    raftVersion,
-		name:       name,
-		url:        url,
-		listenHost: listenHost,
-		tlsConf:    tlsConf,
-		tlsInfo:    tlsInfo,
-		followersStats: &raftFollowersStats{
-			Leader:    name,
-			Followers: make(map[string]*raftFollowerStats),
-		},
-		serverStats: &raftServerStats{
-			StartTime: time.Now(),
-			sendRateQueue: &statsQueue{
-				back: -1,
-			},
-			recvRateQueue: &statsQueue{
-				back: -1,
-			},
-		},
-	}
-
-	// Create transporter for raft
-	raftTransporter := newTransporter(tlsConf.Scheme, tlsConf.Client, raftWrapper)
-
-	// Create raft server
-	server, err := raft.NewServer(name, dirPath, raftTransporter, etcdStore, raftWrapper, "")
-	check(err)
-
-	raftWrapper.Server = server
-
-	return raftWrapper
-}
-
-// Start the raft server
-func (r *raftServer) ListenAndServe() {
-	// Setup commands.
-	registerCommands()
-
-	// LoadSnapshot
-	if snapshot {
-		err := r.LoadSnapshot()
-
-		if err == nil {
-			debugf("%s finished load snapshot", r.name)
-		} else {
-			debug(err)
-		}
-	}
-
-	r.SetElectionTimeout(ElectionTimeout)
-	r.SetHeartbeatTimeout(HeartbeatTimeout)
-
-	r.Start()
-
-	if r.IsLogEmpty() {
-
-		// start as a leader in a new cluster
-		if len(cluster) == 0 {
-			r.startAsLeader()
-
-		} else {
-			r.startAsFollower()
-		}
-
-	} else {
-
-		// rejoin the previous cluster
-		cluster = r.getMachines(nameToRaftURL)
-		for i := 0; i < len(cluster); i++ {
-			u, err := url.Parse(cluster[i])
-			if err != nil {
-				debug("rejoin cannot parse url: ", err)
-			}
-			cluster[i] = u.Host
-		}
-		ok := r.joinCluster(cluster)
-		if !ok {
-			warn("the entire cluster is down! this machine will restart the cluster.")
-		}
-
-		debugf("%s restart as a follower", r.name)
-	}
-
-	// open the snapshot
-	if snapshot {
-		go r.monitorSnapshot()
-	}
-
-	// start to response to raft requests
-	go r.startTransport(r.tlsConf.Scheme, r.tlsConf.Server)
-
-}
-
-func (r *raftServer) startAsLeader() {
-	// leader need to join self as a peer
-	for {
-		_, err := r.Do(newJoinCommand(r.version, r.Name(), r.url, e.url))
-		if err == nil {
-			break
-		}
-	}
-	debugf("%s start as a leader", r.name)
-}
-
-func (r *raftServer) startAsFollower() {
-	// start as a follower in a existing cluster
-	for i := 0; i < retryTimes; i++ {
-		ok := r.joinCluster(cluster)
-		if ok {
-			return
-		}
-		warnf("cannot join to cluster via given machines, retry in %d seconds", RetryInterval)
-		time.Sleep(time.Second * RetryInterval)
-	}
-
-	fatalf("Cannot join the cluster via given machines after %x retries", retryTimes)
-}
-
-// Start to listen and response raft command
-func (r *raftServer) startTransport(scheme string, tlsConf tls.Config) {
-	infof("raft server [name %s, listen on %s, advertised url %s]", r.name, r.listenHost, r.url)
-
-	raftMux := http.NewServeMux()
-
-	server := &http.Server{
-		Handler:   raftMux,
-		TLSConfig: &tlsConf,
-		Addr:      r.listenHost,
-	}
-
-	// internal commands
-	raftMux.HandleFunc("/name", r.NameHttpHandler)
-	raftMux.HandleFunc("/version", r.RaftVersionHttpHandler)
-	raftMux.Handle("/join", errorHandler(r.JoinHttpHandler))
-	raftMux.HandleFunc("/remove/", r.RemoveHttpHandler)
-	raftMux.HandleFunc("/vote", r.VoteHttpHandler)
-	raftMux.HandleFunc("/log", r.GetLogHttpHandler)
-	raftMux.HandleFunc("/log/append", r.AppendEntriesHttpHandler)
-	raftMux.HandleFunc("/snapshot", r.SnapshotHttpHandler)
-	raftMux.HandleFunc("/snapshotRecovery", r.SnapshotRecoveryHttpHandler)
-	raftMux.HandleFunc("/etcdURL", r.EtcdURLHttpHandler)
-
-	if scheme == "http" {
-		fatal(server.ListenAndServe())
-	} else {
-		fatal(server.ListenAndServeTLS(r.tlsInfo.CertFile, r.tlsInfo.KeyFile))
-	}
-
-}
-
-// getVersion fetches the raft version of a peer. This works for now but we
-// will need to do something more sophisticated later when we allow mixed
-// version clusters.
-func getVersion(t *transporter, versionURL url.URL) (string, error) {
-	resp, req, err := t.Get(versionURL.String())
-
-	if err != nil {
-		return "", err
-	}
-
-	defer resp.Body.Close()
-
-	t.CancelWhenTimeout(req)
-
-	body, err := ioutil.ReadAll(resp.Body)
-
-	return string(body), nil
-}
-
-func (r *raftServer) joinCluster(cluster []string) bool {
-	for _, machine := range cluster {
-
-		if len(machine) == 0 {
-			continue
-		}
-
-		err := r.joinByMachine(r.Server, machine, r.tlsConf.Scheme)
-		if err == nil {
-			debugf("%s success join to the cluster via machine %s", r.name, machine)
-			return true
-
-		} else {
-			if _, ok := err.(etcdErr.Error); ok {
-				fatal(err)
-			}
-
-			debugf("cannot join to cluster via machine %s %s", machine, err)
-		}
-	}
-	return false
-}
-
-// Send join requests to machine.
-func (r *raftServer) joinByMachine(s *raft.Server, machine string, scheme string) error {
-	var b bytes.Buffer
-
-	// t must be ok
-	t, _ := r.Transporter().(*transporter)
-
-	// Our version must match the leaders version
-	versionURL := url.URL{Host: machine, Scheme: scheme, Path: "/version"}
-	version, err := getVersion(t, versionURL)
-	if err != nil {
-		return fmt.Errorf("Unable to join: %v", err)
-	}
-
-	// TODO: versioning of the internal protocol. See:
-	// Documentation/internatl-protocol-versioning.md
-	if version != r.version {
-		return fmt.Errorf("Unable to join: internal version mismatch, entire cluster must be running identical versions of etcd")
-	}
-
-	json.NewEncoder(&b).Encode(newJoinCommand(r.version, r.Name(), r.url, e.url))
-
-	joinURL := url.URL{Host: machine, Scheme: scheme, Path: "/join"}
-
-	debugf("Send Join Request to %s", joinURL.String())
-
-	resp, req, err := t.Post(joinURL.String(), &b)
-
-	for {
-		if err != nil {
-			return fmt.Errorf("Unable to join: %v", err)
-		}
-		if resp != nil {
-			defer resp.Body.Close()
-
-			t.CancelWhenTimeout(req)
-
-			if resp.StatusCode == http.StatusOK {
-				b, _ := ioutil.ReadAll(resp.Body)
-				r.joinIndex, _ = binary.Uvarint(b)
-				return nil
-			}
-			if resp.StatusCode == http.StatusTemporaryRedirect {
-
-				address := resp.Header.Get("Location")
-				debugf("Send Join Request to %s", address)
-
-				json.NewEncoder(&b).Encode(newJoinCommand(r.version, r.Name(), r.url, e.url))
-
-				resp, req, err = t.Post(address, &b)
-
-			} else if resp.StatusCode == http.StatusBadRequest {
-				debug("Reach max number machines in the cluster")
-				decoder := json.NewDecoder(resp.Body)
-				err := &etcdErr.Error{}
-				decoder.Decode(err)
-				return *err
-			} else {
-				return fmt.Errorf("Unable to join")
-			}
-		}
-
-	}
-	return fmt.Errorf("Unable to join: %v", err)
-}
-
-func (r *raftServer) Stats() []byte {
-	r.serverStats.LeaderInfo.Uptime = time.Now().Sub(r.serverStats.LeaderInfo.startTime).String()
-
-	queue := r.serverStats.sendRateQueue
-
-	r.serverStats.SendingPkgRate, r.serverStats.SendingBandwidthRate = queue.Rate()
-
-	queue = r.serverStats.recvRateQueue
-
-	r.serverStats.RecvingPkgRate, r.serverStats.RecvingBandwidthRate = queue.Rate()
-
-	b, _ := json.Marshal(r.serverStats)
-
-	return b
-}
-
-func (r *raftServer) PeerStats() []byte {
-	if r.State() == raft.Leader {
-		b, _ := json.Marshal(r.followersStats)
-		return b
-	}
-	return nil
-}
-
-// Register commands to raft server
-func registerCommands() {
-	raft.RegisterCommand(&JoinCommand{})
-	raft.RegisterCommand(&RemoveCommand{})
-	raft.RegisterCommand(&GetCommand{})
-	raft.RegisterCommand(&DeleteCommand{})
-	raft.RegisterCommand(&WatchCommand{})
-	raft.RegisterCommand(&TestAndSetCommand{})
-
-	raft.RegisterCommand(&CreateCommand{})
-	raft.RegisterCommand(&UpdateCommand{})
-}

+ 0 - 210
raft_stats.go

@@ -1,210 +0,0 @@
-package main
-
-import (
-	"math"
-	"sync"
-	"time"
-
-	"github.com/coreos/go-raft"
-)
-
-const (
-	queueCapacity = 200
-)
-
-// packageStats represent the stats we need for a package.
-// It has sending time and the size of the package.
-type packageStats struct {
-	sendingTime time.Time
-	size        int
-}
-
-// NewPackageStats creates a pacakgeStats and return the pointer to it.
-func NewPackageStats(now time.Time, size int) *packageStats {
-	return &packageStats{
-		sendingTime: now,
-		size:        size,
-	}
-}
-
-// Time return the sending time of the package.
-func (ps *packageStats) Time() time.Time {
-	return ps.sendingTime
-}
-
-type raftServerStats struct {
-	Name      string    `json:"name"`
-	State     string    `json:"state"`
-	StartTime time.Time `json:"startTime"`
-
-	LeaderInfo struct {
-		Name      string `json:"leader"`
-		Uptime    string `json:"uptime"`
-		startTime time.Time
-	} `json:"leaderInfo"`
-
-	RecvAppendRequestCnt uint64  `json:"recvAppendRequestCnt,"`
-	RecvingPkgRate       float64 `json:"recvPkgRate,omitempty"`
-	RecvingBandwidthRate float64 `json:"recvBandwidthRate,omitempty"`
-
-	SendAppendRequestCnt uint64  `json:"sendAppendRequestCnt"`
-	SendingPkgRate       float64 `json:"sendPkgRate,omitempty"`
-	SendingBandwidthRate float64 `json:"sendBandwidthRate,omitempty"`
-
-	sendRateQueue *statsQueue
-	recvRateQueue *statsQueue
-}
-
-func (ss *raftServerStats) RecvAppendReq(leaderName string, pkgSize int) {
-	ss.State = raft.Follower
-	if leaderName != ss.LeaderInfo.Name {
-		ss.LeaderInfo.Name = leaderName
-		ss.LeaderInfo.startTime = time.Now()
-	}
-
-	ss.recvRateQueue.Insert(NewPackageStats(time.Now(), pkgSize))
-	ss.RecvAppendRequestCnt++
-}
-
-func (ss *raftServerStats) SendAppendReq(pkgSize int) {
-	now := time.Now()
-
-	if ss.State != raft.Leader {
-		ss.State = raft.Leader
-		ss.LeaderInfo.Name = ss.Name
-		ss.LeaderInfo.startTime = now
-	}
-
-	ss.sendRateQueue.Insert(NewPackageStats(now, pkgSize))
-
-	ss.SendAppendRequestCnt++
-}
-
-type raftFollowersStats struct {
-	Leader    string                        `json:"leader"`
-	Followers map[string]*raftFollowerStats `json:"followers"`
-}
-
-type raftFollowerStats struct {
-	Latency struct {
-		Current           float64 `json:"current"`
-		Average           float64 `json:"average"`
-		averageSquare     float64
-		StandardDeviation float64 `json:"standardDeviation"`
-		Minimum           float64 `json:"minimum"`
-		Maximum           float64 `json:"maximum"`
-	} `json:"latency"`
-
-	Counts struct {
-		Fail    uint64 `json:"fail"`
-		Success uint64 `json:"success"`
-	} `json:"counts"`
-}
-
-// Succ function update the raftFollowerStats with a successful send
-func (ps *raftFollowerStats) Succ(d time.Duration) {
-	total := float64(ps.Counts.Success) * ps.Latency.Average
-	totalSquare := float64(ps.Counts.Success) * ps.Latency.averageSquare
-
-	ps.Counts.Success++
-
-	ps.Latency.Current = float64(d) / (1000000.0)
-
-	if ps.Latency.Current > ps.Latency.Maximum {
-		ps.Latency.Maximum = ps.Latency.Current
-	}
-
-	if ps.Latency.Current < ps.Latency.Minimum {
-		ps.Latency.Minimum = ps.Latency.Current
-	}
-
-	ps.Latency.Average = (total + ps.Latency.Current) / float64(ps.Counts.Success)
-	ps.Latency.averageSquare = (totalSquare + ps.Latency.Current*ps.Latency.Current) / float64(ps.Counts.Success)
-
-	// sdv = sqrt(avg(x^2) - avg(x)^2)
-	ps.Latency.StandardDeviation = math.Sqrt(ps.Latency.averageSquare - ps.Latency.Average*ps.Latency.Average)
-}
-
-// Fail function update the raftFollowerStats with a unsuccessful send
-func (ps *raftFollowerStats) Fail() {
-	ps.Counts.Fail++
-}
-
-type statsQueue struct {
-	items        [queueCapacity]*packageStats
-	size         int
-	front        int
-	back         int
-	totalPkgSize int
-	rwl          sync.RWMutex
-}
-
-func (q *statsQueue) Len() int {
-	return q.size
-}
-
-func (q *statsQueue) PkgSize() int {
-	return q.totalPkgSize
-}
-
-// FrontAndBack gets the front and back elements in the queue
-// We must grab front and back together with the protection of the lock
-func (q *statsQueue) frontAndBack() (*packageStats, *packageStats) {
-	q.rwl.RLock()
-	defer q.rwl.RUnlock()
-	if q.size != 0 {
-		return q.items[q.front], q.items[q.back]
-	}
-	return nil, nil
-}
-
-// Insert function insert a packageStats into the queue and update the records
-func (q *statsQueue) Insert(p *packageStats) {
-	q.rwl.Lock()
-	defer q.rwl.Unlock()
-
-	q.back = (q.back + 1) % queueCapacity
-
-	if q.size == queueCapacity { //dequeue
-		q.totalPkgSize -= q.items[q.front].size
-		q.front = (q.back + 1) % queueCapacity
-	} else {
-		q.size++
-	}
-
-	q.items[q.back] = p
-	q.totalPkgSize += q.items[q.back].size
-
-}
-
-// Rate function returns the package rate and byte rate
-func (q *statsQueue) Rate() (float64, float64) {
-	front, back := q.frontAndBack()
-
-	if front == nil || back == nil {
-		return 0, 0
-	}
-
-	if time.Now().Sub(back.Time()) > time.Second {
-		q.Clear()
-		return 0, 0
-	}
-
-	sampleDuration := back.Time().Sub(front.Time())
-
-	pr := float64(q.Len()) / float64(sampleDuration) * float64(time.Second)
-
-	br := float64(q.PkgSize()) / float64(sampleDuration) * float64(time.Second)
-
-	return pr, br
-}
-
-// Clear function clear up the statsQueue
-func (q *statsQueue) Clear() {
-	q.rwl.Lock()
-	defer q.rwl.Unlock()
-	q.back = -1
-	q.front = 0
-	q.size = 0
-	q.totalPkgSize = 0
-}

+ 1 - 1
scripts/release-version

@@ -3,6 +3,6 @@
 VER=$(git describe --tags HEAD)
 VER=$(git describe --tags HEAD)
 
 
 cat <<EOF
 cat <<EOF
-package main
+package server
 const releaseVersion = "$VER"
 const releaseVersion = "$VER"
 EOF
 EOF

+ 75 - 0
server/join_command.go

@@ -0,0 +1,75 @@
+package server
+
+import (
+	"encoding/binary"
+
+	etcdErr "github.com/coreos/etcd/error"
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/go-raft"
+)
+
+func init() {
+	raft.RegisterCommand(&JoinCommand{})
+}
+
+// The JoinCommand adds a node to the cluster.
+type JoinCommand struct {
+	RaftVersion string `json:"raftVersion"`
+	Name        string `json:"name"`
+	RaftURL     string `json:"raftURL"`
+	EtcdURL     string `json:"etcdURL"`
+}
+
+func NewJoinCommand(version, name, raftUrl, etcdUrl string) *JoinCommand {
+	return &JoinCommand{
+		RaftVersion: version,
+		Name:        name,
+		RaftURL:     raftUrl,
+		EtcdURL:     etcdUrl,
+	}
+}
+
+// The name of the join command in the log
+func (c *JoinCommand) CommandName() string {
+	return "etcd:join"
+}
+
+// Join a server to the cluster
+func (c *JoinCommand) Apply(server *raft.Server) (interface{}, error) {
+	ps, _ := server.Context().(*PeerServer)
+
+	b := make([]byte, 8)
+	binary.PutUvarint(b, server.CommitIndex())
+
+	// Make sure we're not getting a cached value from the registry.
+	ps.registry.Invalidate(c.Name)
+
+	// Check if the join command is from a previous machine, who lost all its previous log.
+	if _, ok := ps.registry.URL(c.Name); ok {
+		return b, nil
+	}
+
+	// Check machine number in the cluster
+	if ps.registry.Count() == ps.MaxClusterSize {
+		log.Debug("Reject join request from ", c.Name)
+		return []byte{0}, etcdErr.NewError(etcdErr.EcodeNoMoreMachine, "", server.CommitIndex(), server.Term())
+	}
+
+	// Add to shared machine registry.
+	ps.registry.Register(c.Name, c.RaftVersion, c.RaftURL, c.EtcdURL, server.CommitIndex(), server.Term())
+
+	// Add peer in raft
+	err := server.AddPeer(c.Name, "")
+
+	// Add peer stats
+	if c.Name != ps.Name() {
+		ps.followersStats.Followers[c.Name] = &raftFollowerStats{}
+		ps.followersStats.Followers[c.Name].Latency.Minimum = 1 << 63
+	}
+
+	return b, err
+}
+
+func (c *JoinCommand) NodeName() string {
+	return c.Name
+}

+ 25 - 0
server/package_stats.go

@@ -0,0 +1,25 @@
+package server
+
+import (
+	"time"
+)
+
+// packageStats represent the stats we need for a package.
+// It has sending time and the size of the package.
+type packageStats struct {
+	sendingTime time.Time
+	size        int
+}
+
+// NewPackageStats creates a pacakgeStats and return the pointer to it.
+func NewPackageStats(now time.Time, size int) *packageStats {
+	return &packageStats{
+		sendingTime: now,
+		size:        size,
+	}
+}
+
+// Time return the sending time of the package.
+func (ps *packageStats) Time() time.Time {
+	return ps.sendingTime
+}

+ 543 - 0
server/peer_server.go

@@ -0,0 +1,543 @@
+package server
+
+import (
+	"bytes"
+	"crypto/tls"
+	"encoding/binary"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"time"
+
+	etcdErr "github.com/coreos/etcd/error"
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/etcd/store"
+	"github.com/coreos/go-raft"
+)
+
+type PeerServer struct {
+	*raft.Server
+	server         *Server
+	joinIndex      uint64
+	name           string
+	url            string
+	listenHost     string
+	tlsConf        *TLSConfig
+	tlsInfo        *TLSInfo
+	followersStats *raftFollowersStats
+	serverStats    *raftServerStats
+	registry       *Registry
+	store          *store.Store
+	snapConf       *snapshotConf
+	MaxClusterSize int
+	RetryTimes     int
+}
+
+// TODO: find a good policy to do snapshot
+type snapshotConf struct {
+	// Etcd will check if snapshot is need every checkingInterval
+	checkingInterval time.Duration
+
+	// The number of writes when the last snapshot happened
+	lastWrites uint64
+
+	// If the incremental number of writes since the last snapshot
+	// exceeds the write Threshold, etcd will do a snapshot
+	writesThr uint64
+}
+
+func NewPeerServer(name string, path string, url string, listenHost string, tlsConf *TLSConfig, tlsInfo *TLSInfo, registry *Registry, store *store.Store) *PeerServer {
+	s := &PeerServer{
+		name:       name,
+		url:        url,
+		listenHost: listenHost,
+		tlsConf:    tlsConf,
+		tlsInfo:    tlsInfo,
+		registry:   registry,
+		store:      store,
+		snapConf:   &snapshotConf{time.Second * 3, 0, 20 * 1000},
+		followersStats: &raftFollowersStats{
+			Leader:    name,
+			Followers: make(map[string]*raftFollowerStats),
+		},
+		serverStats: &raftServerStats{
+			StartTime: time.Now(),
+			sendRateQueue: &statsQueue{
+				back: -1,
+			},
+			recvRateQueue: &statsQueue{
+				back: -1,
+			},
+		},
+	}
+
+	// Create transporter for raft
+	raftTransporter := newTransporter(tlsConf.Scheme, tlsConf.Client, s)
+
+	// Create raft server
+	server, err := raft.NewServer(name, path, raftTransporter, s.store, s, "")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	s.Server = server
+
+	return s
+}
+
+// Start the raft server
+func (s *PeerServer) ListenAndServe(snapshot bool, cluster []string) {
+	// LoadSnapshot
+	if snapshot {
+		err := s.LoadSnapshot()
+
+		if err == nil {
+			log.Debugf("%s finished load snapshot", s.name)
+		} else {
+			log.Debug(err)
+		}
+	}
+
+	s.SetElectionTimeout(ElectionTimeout)
+	s.SetHeartbeatTimeout(HeartbeatTimeout)
+
+	s.Start()
+
+	if s.IsLogEmpty() {
+		// start as a leader in a new cluster
+		if len(cluster) == 0 {
+			s.startAsLeader()
+		} else {
+			s.startAsFollower(cluster)
+		}
+
+	} else {
+		// Rejoin the previous cluster
+		cluster = s.registry.PeerURLs(s.Leader(), s.name)
+		for i := 0; i < len(cluster); i++ {
+			u, err := url.Parse(cluster[i])
+			if err != nil {
+				log.Debug("rejoin cannot parse url: ", err)
+			}
+			cluster[i] = u.Host
+		}
+		ok := s.joinCluster(cluster)
+		if !ok {
+			log.Warn("the entire cluster is down! this machine will restart the cluster.")
+		}
+
+		log.Debugf("%s restart as a follower", s.name)
+	}
+
+	// open the snapshot
+	if snapshot {
+		go s.monitorSnapshot()
+	}
+
+	// start to response to raft requests
+	go s.startTransport(s.tlsConf.Scheme, s.tlsConf.Server)
+
+}
+
+// Retrieves the underlying Raft server.
+func (s *PeerServer) RaftServer() *raft.Server {
+	return s.Server
+}
+
+// Associates the client server with the peer server.
+func (s *PeerServer) SetServer(server *Server) {
+	s.server = server
+}
+
+// Get all the current logs
+func (s *PeerServer) GetLogHttpHandler(w http.ResponseWriter, req *http.Request) {
+	log.Debugf("[recv] GET %s/log", s.url)
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(s.LogEntries())
+}
+
+// Response to vote request
+func (s *PeerServer) VoteHttpHandler(w http.ResponseWriter, req *http.Request) {
+	rvreq := &raft.RequestVoteRequest{}
+	err := decodeJsonRequest(req, rvreq)
+	if err == nil {
+		log.Debugf("[recv] POST %s/vote [%s]", s.url, rvreq.CandidateName)
+		if resp := s.RequestVote(rvreq); resp != nil {
+			w.WriteHeader(http.StatusOK)
+			json.NewEncoder(w).Encode(resp)
+			return
+		}
+	}
+	log.Warnf("[vote] ERROR: %v", err)
+	w.WriteHeader(http.StatusInternalServerError)
+}
+
+// Response to append entries request
+func (s *PeerServer) AppendEntriesHttpHandler(w http.ResponseWriter, req *http.Request) {
+	aereq := &raft.AppendEntriesRequest{}
+	err := decodeJsonRequest(req, aereq)
+
+	if err == nil {
+		log.Debugf("[recv] POST %s/log/append [%d]", s.url, len(aereq.Entries))
+
+		s.serverStats.RecvAppendReq(aereq.LeaderName, int(req.ContentLength))
+
+		if resp := s.AppendEntries(aereq); resp != nil {
+			w.WriteHeader(http.StatusOK)
+			json.NewEncoder(w).Encode(resp)
+			if !resp.Success {
+				log.Debugf("[Append Entry] Step back")
+			}
+			return
+		}
+	}
+	log.Warnf("[Append Entry] ERROR: %v", err)
+	w.WriteHeader(http.StatusInternalServerError)
+}
+
+// Response to recover from snapshot request
+func (s *PeerServer) SnapshotHttpHandler(w http.ResponseWriter, req *http.Request) {
+	aereq := &raft.SnapshotRequest{}
+	err := decodeJsonRequest(req, aereq)
+	if err == nil {
+		log.Debugf("[recv] POST %s/snapshot/ ", s.url)
+		if resp := s.RequestSnapshot(aereq); resp != nil {
+			w.WriteHeader(http.StatusOK)
+			json.NewEncoder(w).Encode(resp)
+			return
+		}
+	}
+	log.Warnf("[Snapshot] ERROR: %v", err)
+	w.WriteHeader(http.StatusInternalServerError)
+}
+
+// Response to recover from snapshot request
+func (s *PeerServer) SnapshotRecoveryHttpHandler(w http.ResponseWriter, req *http.Request) {
+	aereq := &raft.SnapshotRecoveryRequest{}
+	err := decodeJsonRequest(req, aereq)
+	if err == nil {
+		log.Debugf("[recv] POST %s/snapshotRecovery/ ", s.url)
+		if resp := s.SnapshotRecoveryRequest(aereq); resp != nil {
+			w.WriteHeader(http.StatusOK)
+			json.NewEncoder(w).Encode(resp)
+			return
+		}
+	}
+	log.Warnf("[Snapshot] ERROR: %v", err)
+	w.WriteHeader(http.StatusInternalServerError)
+}
+
+// Get the port that listening for etcd connecting of the server
+func (s *PeerServer) EtcdURLHttpHandler(w http.ResponseWriter, req *http.Request) {
+	log.Debugf("[recv] Get %s/etcdURL/ ", s.url)
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte(s.server.URL()))
+}
+
+// Response to the join request
+func (s *PeerServer) JoinHttpHandler(w http.ResponseWriter, req *http.Request) {
+	command := &JoinCommand{}
+
+	// Write CORS header.
+	if s.server.OriginAllowed("*") {
+		w.Header().Add("Access-Control-Allow-Origin", "*")
+	} else if s.server.OriginAllowed(req.Header.Get("Origin")) {
+		w.Header().Add("Access-Control-Allow-Origin", req.Header.Get("Origin"))
+	}
+
+	err := decodeJsonRequest(req, command)
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	log.Debugf("Receive Join Request from %s", command.Name)
+	err = s.dispatchRaftCommand(command, w, req)
+
+	// Return status.
+	if err != nil {
+		if etcdErr, ok := err.(*etcdErr.Error); ok {
+			log.Debug("Return error: ", (*etcdErr).Error())
+			etcdErr.Write(w)
+		} else {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+		}
+	}
+}
+
+// Response to remove request
+func (s *PeerServer) RemoveHttpHandler(w http.ResponseWriter, req *http.Request) {
+	if req.Method != "DELETE" {
+		w.WriteHeader(http.StatusMethodNotAllowed)
+		return
+	}
+
+	nodeName := req.URL.Path[len("/remove/"):]
+	command := &RemoveCommand{
+		Name: nodeName,
+	}
+
+	log.Debugf("[recv] Remove Request [%s]", command.Name)
+
+	s.dispatchRaftCommand(command, w, req)
+}
+
+// Response to the name request
+func (s *PeerServer) NameHttpHandler(w http.ResponseWriter, req *http.Request) {
+	log.Debugf("[recv] Get %s/name/ ", s.url)
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte(s.name))
+}
+
+// Response to the name request
+func (s *PeerServer) RaftVersionHttpHandler(w http.ResponseWriter, req *http.Request) {
+	log.Debugf("[recv] Get %s/version/ ", s.url)
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte(PeerVersion))
+}
+
+func (s *PeerServer) dispatchRaftCommand(c raft.Command, w http.ResponseWriter, req *http.Request) error {
+	return s.dispatch(c, w, req)
+}
+
+func (s *PeerServer) startAsLeader() {
+	// leader need to join self as a peer
+	for {
+		_, err := s.Do(NewJoinCommand(PeerVersion, s.Name(), s.url, s.server.URL()))
+		if err == nil {
+			break
+		}
+	}
+	log.Debugf("%s start as a leader", s.name)
+}
+
+func (s *PeerServer) startAsFollower(cluster []string) {
+	// start as a follower in a existing cluster
+	for i := 0; i < s.RetryTimes; i++ {
+		ok := s.joinCluster(cluster)
+		if ok {
+			return
+		}
+		log.Warnf("cannot join to cluster via given machines, retry in %d seconds", RetryInterval)
+		time.Sleep(time.Second * RetryInterval)
+	}
+
+	log.Fatalf("Cannot join the cluster via given machines after %x retries", s.RetryTimes)
+}
+
+// Start to listen and response raft command
+func (s *PeerServer) startTransport(scheme string, tlsConf tls.Config) {
+	log.Infof("raft server [name %s, listen on %s, advertised url %s]", s.name, s.listenHost, s.url)
+
+	raftMux := http.NewServeMux()
+
+	server := &http.Server{
+		Handler:   raftMux,
+		TLSConfig: &tlsConf,
+		Addr:      s.listenHost,
+	}
+
+	// internal commands
+	raftMux.HandleFunc("/name", s.NameHttpHandler)
+	raftMux.HandleFunc("/version", s.RaftVersionHttpHandler)
+	raftMux.HandleFunc("/join", s.JoinHttpHandler)
+	raftMux.HandleFunc("/remove/", s.RemoveHttpHandler)
+	raftMux.HandleFunc("/vote", s.VoteHttpHandler)
+	raftMux.HandleFunc("/log", s.GetLogHttpHandler)
+	raftMux.HandleFunc("/log/append", s.AppendEntriesHttpHandler)
+	raftMux.HandleFunc("/snapshot", s.SnapshotHttpHandler)
+	raftMux.HandleFunc("/snapshotRecovery", s.SnapshotRecoveryHttpHandler)
+	raftMux.HandleFunc("/etcdURL", s.EtcdURLHttpHandler)
+
+	if scheme == "http" {
+		log.Fatal(server.ListenAndServe())
+	} else {
+		log.Fatal(server.ListenAndServeTLS(s.tlsInfo.CertFile, s.tlsInfo.KeyFile))
+	}
+
+}
+
+// getVersion fetches the raft version of a peer. This works for now but we
+// will need to do something more sophisticated later when we allow mixed
+// version clusters.
+func getVersion(t *transporter, versionURL url.URL) (string, error) {
+	resp, req, err := t.Get(versionURL.String())
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+
+	t.CancelWhenTimeout(req)
+
+	body, err := ioutil.ReadAll(resp.Body)
+
+	return string(body), nil
+}
+
+func (s *PeerServer) joinCluster(cluster []string) bool {
+	for _, machine := range cluster {
+		if len(machine) == 0 {
+			continue
+		}
+
+		err := s.joinByMachine(s.Server, machine, s.tlsConf.Scheme)
+		if err == nil {
+			log.Debugf("%s success join to the cluster via machine %s", s.name, machine)
+			return true
+
+		} else {
+			if _, ok := err.(etcdErr.Error); ok {
+				log.Fatal(err)
+			}
+
+			log.Debugf("cannot join to cluster via machine %s %s", machine, err)
+		}
+	}
+	return false
+}
+
+// Send join requests to machine.
+func (s *PeerServer) joinByMachine(server *raft.Server, machine string, scheme string) error {
+	var b bytes.Buffer
+
+	// t must be ok
+	t, _ := server.Transporter().(*transporter)
+
+	// Our version must match the leaders version
+	versionURL := url.URL{Host: machine, Scheme: scheme, Path: "/version"}
+	version, err := getVersion(t, versionURL)
+	if err != nil {
+		return fmt.Errorf("Error during join version check: %v", err)
+	}
+
+	// TODO: versioning of the internal protocol. See:
+	// Documentation/internatl-protocol-versioning.md
+	if version != PeerVersion {
+		return fmt.Errorf("Unable to join: internal version mismatch, entire cluster must be running identical versions of etcd")
+	}
+
+	json.NewEncoder(&b).Encode(NewJoinCommand(PeerVersion, server.Name(), s.url, s.server.URL()))
+
+	joinURL := url.URL{Host: machine, Scheme: scheme, Path: "/join"}
+
+	log.Debugf("Send Join Request to %s", joinURL.String())
+
+	resp, req, err := t.Post(joinURL.String(), &b)
+
+	for {
+		if err != nil {
+			return fmt.Errorf("Unable to join: %v", err)
+		}
+		if resp != nil {
+			defer resp.Body.Close()
+
+			t.CancelWhenTimeout(req)
+
+			if resp.StatusCode == http.StatusOK {
+				b, _ := ioutil.ReadAll(resp.Body)
+				s.joinIndex, _ = binary.Uvarint(b)
+				return nil
+			}
+			if resp.StatusCode == http.StatusTemporaryRedirect {
+				address := resp.Header.Get("Location")
+				log.Debugf("Send Join Request to %s", address)
+				json.NewEncoder(&b).Encode(NewJoinCommand(PeerVersion, server.Name(), s.url, s.server.URL()))
+				resp, req, err = t.Post(address, &b)
+
+			} else if resp.StatusCode == http.StatusBadRequest {
+				log.Debug("Reach max number machines in the cluster")
+				decoder := json.NewDecoder(resp.Body)
+				err := &etcdErr.Error{}
+				decoder.Decode(err)
+				return *err
+			} else {
+				return fmt.Errorf("Unable to join")
+			}
+		}
+
+	}
+}
+
+func (s *PeerServer) Stats() []byte {
+	s.serverStats.LeaderInfo.Uptime = time.Now().Sub(s.serverStats.LeaderInfo.startTime).String()
+
+	queue := s.serverStats.sendRateQueue
+
+	s.serverStats.SendingPkgRate, s.serverStats.SendingBandwidthRate = queue.Rate()
+
+	queue = s.serverStats.recvRateQueue
+
+	s.serverStats.RecvingPkgRate, s.serverStats.RecvingBandwidthRate = queue.Rate()
+
+	b, _ := json.Marshal(s.serverStats)
+
+	return b
+}
+
+func (s *PeerServer) PeerStats() []byte {
+	if s.State() == raft.Leader {
+		b, _ := json.Marshal(s.followersStats)
+		return b
+	}
+	return nil
+}
+
+func (s *PeerServer) monitorSnapshot() {
+	for {
+		time.Sleep(s.snapConf.checkingInterval)
+		currentWrites := 0
+		if uint64(currentWrites) > s.snapConf.writesThr {
+			s.TakeSnapshot()
+			s.snapConf.lastWrites = 0
+		}
+	}
+}
+
+func (s *PeerServer) dispatch(c raft.Command, w http.ResponseWriter, req *http.Request) error {
+	if s.State() == raft.Leader {
+		if response, err := s.Do(c); err != nil {
+			return err
+		} else {
+			if response == nil {
+				return etcdErr.NewError(300, "Empty response from raft", store.UndefIndex, store.UndefTerm)
+			}
+
+			event, ok := response.(*store.Event)
+			if ok {
+				bytes, err := json.Marshal(event)
+				if err != nil {
+					fmt.Println(err)
+				}
+
+				w.Header().Add("X-Etcd-Index", fmt.Sprint(event.Index))
+				w.Header().Add("X-Etcd-Term", fmt.Sprint(event.Term))
+				w.WriteHeader(http.StatusOK)
+				w.Write(bytes)
+
+				return nil
+			}
+
+			bytes, _ := response.([]byte)
+			w.WriteHeader(http.StatusOK)
+			w.Write(bytes)
+
+			return nil
+		}
+
+	} else {
+		leader := s.Leader()
+		// current no leader
+		if leader == "" {
+			return etcdErr.NewError(300, "", store.UndefIndex, store.UndefTerm)
+		}
+		url, _ := s.registry.PeerURL(leader)
+
+		log.Debugf("Not leader; Current leader: %s; redirect: %s", leader, url)
+		redirect(url, w, req)
+
+		return nil
+	}
+}

+ 56 - 0
server/raft_follower_stats.go

@@ -0,0 +1,56 @@
+package server
+
+import (
+	"math"
+	"time"
+)
+
+type raftFollowersStats struct {
+	Leader    string                        `json:"leader"`
+	Followers map[string]*raftFollowerStats `json:"followers"`
+}
+
+type raftFollowerStats struct {
+	Latency struct {
+		Current           float64 `json:"current"`
+		Average           float64 `json:"average"`
+		averageSquare     float64
+		StandardDeviation float64 `json:"standardDeviation"`
+		Minimum           float64 `json:"minimum"`
+		Maximum           float64 `json:"maximum"`
+	} `json:"latency"`
+
+	Counts struct {
+		Fail    uint64 `json:"fail"`
+		Success uint64 `json:"success"`
+	} `json:"counts"`
+}
+
+// Succ function update the raftFollowerStats with a successful send
+func (ps *raftFollowerStats) Succ(d time.Duration) {
+	total := float64(ps.Counts.Success) * ps.Latency.Average
+	totalSquare := float64(ps.Counts.Success) * ps.Latency.averageSquare
+
+	ps.Counts.Success++
+
+	ps.Latency.Current = float64(d) / (1000000.0)
+
+	if ps.Latency.Current > ps.Latency.Maximum {
+		ps.Latency.Maximum = ps.Latency.Current
+	}
+
+	if ps.Latency.Current < ps.Latency.Minimum {
+		ps.Latency.Minimum = ps.Latency.Current
+	}
+
+	ps.Latency.Average = (total + ps.Latency.Current) / float64(ps.Counts.Success)
+	ps.Latency.averageSquare = (totalSquare + ps.Latency.Current*ps.Latency.Current) / float64(ps.Counts.Success)
+
+	// sdv = sqrt(avg(x^2) - avg(x)^2)
+	ps.Latency.StandardDeviation = math.Sqrt(ps.Latency.averageSquare - ps.Latency.Average*ps.Latency.Average)
+}
+
+// Fail function update the raftFollowerStats with a unsuccessful send
+func (ps *raftFollowerStats) Fail() {
+	ps.Counts.Fail++
+}

+ 55 - 0
server/raft_server_stats.go

@@ -0,0 +1,55 @@
+package server
+
+import (
+	"time"
+
+	"github.com/coreos/go-raft"
+)
+
+type raftServerStats struct {
+	Name      string    `json:"name"`
+	State     string    `json:"state"`
+	StartTime time.Time `json:"startTime"`
+
+	LeaderInfo struct {
+		Name      string `json:"leader"`
+		Uptime    string `json:"uptime"`
+		startTime time.Time
+	} `json:"leaderInfo"`
+
+	RecvAppendRequestCnt uint64  `json:"recvAppendRequestCnt,"`
+	RecvingPkgRate       float64 `json:"recvPkgRate,omitempty"`
+	RecvingBandwidthRate float64 `json:"recvBandwidthRate,omitempty"`
+
+	SendAppendRequestCnt uint64  `json:"sendAppendRequestCnt"`
+	SendingPkgRate       float64 `json:"sendPkgRate,omitempty"`
+	SendingBandwidthRate float64 `json:"sendBandwidthRate,omitempty"`
+
+	sendRateQueue *statsQueue
+	recvRateQueue *statsQueue
+}
+
+func (ss *raftServerStats) RecvAppendReq(leaderName string, pkgSize int) {
+	ss.State = raft.Follower
+	if leaderName != ss.LeaderInfo.Name {
+		ss.LeaderInfo.Name = leaderName
+		ss.LeaderInfo.startTime = time.Now()
+	}
+
+	ss.recvRateQueue.Insert(NewPackageStats(time.Now(), pkgSize))
+	ss.RecvAppendRequestCnt++
+}
+
+func (ss *raftServerStats) SendAppendReq(pkgSize int) {
+	now := time.Now()
+
+	if ss.State != raft.Leader {
+		ss.State = raft.Leader
+		ss.LeaderInfo.Name = ss.Name
+		ss.LeaderInfo.startTime = now
+	}
+
+	ss.sendRateQueue.Insert(NewPackageStats(now, pkgSize))
+
+	ss.SendAppendRequestCnt++
+}

+ 197 - 0
server/registry.go

@@ -0,0 +1,197 @@
+package server
+
+import (
+	"fmt"
+	"net/url"
+	"path"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/etcd/store"
+)
+
+// The location of the machine URL data.
+const RegistryKey = "/_etcd/machines"
+
+// The Registry stores URL information for nodes.
+type Registry struct {
+	sync.Mutex
+	store *store.Store
+	nodes map[string]*node
+}
+
+// The internal storage format of the registry.
+type node struct {
+	peerVersion string
+	peerURL     string
+	url         string
+}
+
+// Creates a new Registry.
+func NewRegistry(s *store.Store) *Registry {
+	return &Registry{
+		store: s,
+		nodes: make(map[string]*node),
+	}
+}
+
+// Adds a node to the registry.
+func (r *Registry) Register(name string, peerVersion string, peerURL string, url string, commitIndex uint64, term uint64) error {
+	r.Lock()
+	defer r.Unlock()
+
+	// Write data to store.
+	key := path.Join(RegistryKey, name)
+	value := fmt.Sprintf("raft=%s&etcd=%s&raftVersion=%s", peerURL, url, peerVersion)
+	_, err := r.store.Create(key, value, false, false, store.Permanent, commitIndex, term)
+	log.Debugf("Register: %s (%v)", name, err)
+	return err
+}
+
+// Removes a node from the registry.
+func (r *Registry) Unregister(name string, commitIndex uint64, term uint64) error {
+	r.Lock()
+	defer r.Unlock()
+
+	// Remove from cache.
+	// delete(r.nodes, name)
+
+	// Remove the key from the store.
+	_, err := r.store.Delete(path.Join(RegistryKey, name), false, commitIndex, term)
+	log.Debugf("Unregister: %s (%v)", name, err)
+	return err
+}
+
+// Returns the number of nodes in the cluster.
+func (r *Registry) Count() int {
+	e, err := r.store.Get(RegistryKey, false, false, 0, 0)
+	if err != nil {
+		return 0
+	}
+	return len(e.KVPairs)
+}
+
+// Retrieves the URL for a given node by name.
+func (r *Registry) URL(name string) (string, bool) {
+	r.Lock()
+	defer r.Unlock()
+	return r.url(name)
+}
+
+func (r *Registry) url(name string) (string, bool) {
+	if r.nodes[name] == nil {
+		r.load(name)
+	}
+
+	if node := r.nodes[name]; node != nil {
+		return node.url, true
+	}
+
+	return "", false
+}
+
+// Retrieves the URLs for all nodes.
+func (r *Registry) URLs(leaderName, selfName string) []string {
+	r.Lock()
+	defer r.Unlock()
+
+	// Build list including the leader and self.
+	urls := make([]string, 0)
+	if url, _ := r.url(leaderName); len(url) > 0 {
+		urls = append(urls, url)
+	}
+
+	// Retrieve a list of all nodes.
+	if e, _ := r.store.Get(RegistryKey, false, false, 0, 0); e != nil {
+		// Lookup the URL for each one.
+		for _, pair := range e.KVPairs {
+			_, name := filepath.Split(pair.Key)
+			if url, _ := r.url(name); len(url) > 0 && name != leaderName {
+				urls = append(urls, url)
+			}
+		}
+	}
+
+	log.Infof("URLs: %s / %s (%s)", leaderName, selfName, strings.Join(urls, ","))
+
+	return urls
+}
+
+// Retrieves the peer URL for a given node by name.
+func (r *Registry) PeerURL(name string) (string, bool) {
+	r.Lock()
+	defer r.Unlock()
+	return r.peerURL(name)
+}
+
+func (r *Registry) peerURL(name string) (string, bool) {
+	if r.nodes[name] == nil {
+		r.load(name)
+	}
+
+	if node := r.nodes[name]; node != nil {
+		return node.peerURL, true
+	}
+
+	return "", false
+}
+
+// Retrieves the peer URLs for all nodes.
+func (r *Registry) PeerURLs(leaderName, selfName string) []string {
+	r.Lock()
+	defer r.Unlock()
+
+	// Build list including the leader and self.
+	urls := make([]string, 0)
+	if url, _ := r.peerURL(leaderName); len(url) > 0 {
+		urls = append(urls, url)
+	}
+
+	// Retrieve a list of all nodes.
+	if e, _ := r.store.Get(RegistryKey, false, false, 0, 0); e != nil {
+		// Lookup the URL for each one.
+		for _, pair := range e.KVPairs {
+			_, name := filepath.Split(pair.Key)
+			if url, _ := r.peerURL(name); len(url) > 0 && name != leaderName {
+				urls = append(urls, url)
+			}
+		}
+	}
+
+	log.Infof("PeerURLs: %s / %s (%s)", leaderName, selfName, strings.Join(urls, ","))
+
+	return urls
+}
+
+// Removes a node from the cache.
+func (r *Registry) Invalidate(name string) {
+	delete(r.nodes, name)
+}
+
+// Loads the given node by name from the store into the cache.
+func (r *Registry) load(name string) {
+	if name == "" {
+		return
+	}
+
+	// Retrieve from store.
+	e, err := r.store.Get(path.Join(RegistryKey, name), false, false, 0, 0)
+	if err != nil {
+		return
+	}
+
+	// Parse as a query string.
+	m, err := url.ParseQuery(e.Value)
+	if err != nil {
+		panic(fmt.Sprintf("Failed to parse machines entry: %s", name))
+	}
+
+	// Create node.
+	r.nodes[name] = &node{
+		url:         m["etcd"][0],
+		peerURL:     m["raft"][0],
+		peerVersion: m["raftVersion"][0],
+	}
+}

+ 67 - 0
server/remove_command.go

@@ -0,0 +1,67 @@
+package server
+
+import (
+	"encoding/binary"
+	"os"
+
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/go-raft"
+)
+
+func init() {
+	raft.RegisterCommand(&RemoveCommand{})
+}
+
+// The RemoveCommand removes a server from the cluster.
+type RemoveCommand struct {
+	Name string `json:"name"`
+}
+
+// The name of the remove command in the log
+func (c *RemoveCommand) CommandName() string {
+	return "etcd:remove"
+}
+
+// Remove a server from the cluster
+func (c *RemoveCommand) Apply(server *raft.Server) (interface{}, error) {
+	ps, _ := server.Context().(*PeerServer)
+
+	// Remove node from the shared registry.
+	err := ps.registry.Unregister(c.Name, server.CommitIndex(), server.Term())
+
+	// Delete from stats
+	delete(ps.followersStats.Followers, c.Name)
+
+	if err != nil {
+		log.Debugf("Error while unregistering: %s (%v)", c.Name, err)
+		return []byte{0}, err
+	}
+
+	// Remove peer in raft
+	err = server.RemovePeer(c.Name)
+	if err != nil {
+		log.Debugf("Unable to remove peer: %s (%v)", c.Name, err)
+		return []byte{0}, err
+	}
+
+	if c.Name == server.Name() {
+		// the removed node is this node
+
+		// if the node is not replaying the previous logs
+		// and the node has sent out a join request in this
+		// start. It is sure that this node received a new remove
+		// command and need to be removed
+		if server.CommitIndex() > ps.joinIndex && ps.joinIndex != 0 {
+			log.Debugf("server [%s] is removed", server.Name())
+			os.Exit(0)
+		} else {
+			// else ignore remove
+			log.Debugf("ignore previous remove command.")
+		}
+	}
+
+	b := make([]byte, 8)
+	binary.PutUvarint(b, server.CommitIndex())
+
+	return b, err
+}

+ 320 - 0
server/server.go

@@ -0,0 +1,320 @@
+package server
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+
+	etcdErr "github.com/coreos/etcd/error"
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/etcd/server/v1"
+	"github.com/coreos/etcd/server/v2"
+	"github.com/coreos/etcd/store"
+	"github.com/coreos/go-raft"
+	"github.com/gorilla/mux"
+)
+
+// This is the default implementation of the Server interface.
+type Server struct {
+	http.Server
+	peerServer  *PeerServer
+	registry    *Registry
+	store       *store.Store
+	name        string
+	url         string
+	tlsConf     *TLSConfig
+	tlsInfo     *TLSInfo
+	corsOrigins map[string]bool
+}
+
+// Creates a new Server.
+func New(name string, urlStr string, listenHost string, tlsConf *TLSConfig, tlsInfo *TLSInfo, peerServer *PeerServer, registry *Registry, store *store.Store) *Server {
+	s := &Server{
+		Server: http.Server{
+			Handler:   mux.NewRouter(),
+			TLSConfig: &tlsConf.Server,
+			Addr:      listenHost,
+		},
+		name:       name,
+		store:      store,
+		registry:   registry,
+		url:        urlStr,
+		tlsConf:    tlsConf,
+		tlsInfo:    tlsInfo,
+		peerServer: peerServer,
+	}
+
+	// Install the routes.
+	s.handleFunc("/version", s.GetVersionHandler).Methods("GET")
+	s.installV1()
+	s.installV2()
+
+	return s
+}
+
+// The current state of the server in the cluster.
+func (s *Server) State() string {
+	return s.peerServer.State()
+}
+
+// The node name of the leader in the cluster.
+func (s *Server) Leader() string {
+	return s.peerServer.Leader()
+}
+
+// The current Raft committed index.
+func (s *Server) CommitIndex() uint64 {
+	return s.peerServer.CommitIndex()
+}
+
+// The current Raft term.
+func (s *Server) Term() uint64 {
+	return s.peerServer.Term()
+}
+
+// The server URL.
+func (s *Server) URL() string {
+	return s.url
+}
+
+// Retrives the Peer URL for a given node name.
+func (s *Server) PeerURL(name string) (string, bool) {
+	return s.registry.PeerURL(name)
+}
+
+// Returns a reference to the Store.
+func (s *Server) Store() *store.Store {
+	return s.store
+}
+
+func (s *Server) installV1() {
+	s.handleFuncV1("/v1/keys/{key:.*}", v1.GetKeyHandler).Methods("GET")
+	s.handleFuncV1("/v1/keys/{key:.*}", v1.SetKeyHandler).Methods("POST", "PUT")
+	s.handleFuncV1("/v1/keys/{key:.*}", v1.DeleteKeyHandler).Methods("DELETE")
+	s.handleFuncV1("/v1/watch/{key:.*}", v1.WatchKeyHandler).Methods("GET", "POST")
+	s.handleFunc("/v1/leader", s.GetLeaderHandler).Methods("GET")
+	s.handleFunc("/v1/machines", s.GetMachinesHandler).Methods("GET")
+	s.handleFunc("/v1/stats/self", s.GetStatsHandler).Methods("GET")
+	s.handleFunc("/v1/stats/leader", s.GetLeaderStatsHandler).Methods("GET")
+	s.handleFunc("/v1/stats/store", s.GetStoreStatsHandler).Methods("GET")
+}
+
+func (s *Server) installV2() {
+	s.handleFuncV2("/v2/keys/{key:.*}", v2.GetKeyHandler).Methods("GET")
+	s.handleFuncV2("/v2/keys/{key:.*}", v2.CreateKeyHandler).Methods("POST")
+	s.handleFuncV2("/v2/keys/{key:.*}", v2.UpdateKeyHandler).Methods("PUT")
+	s.handleFuncV2("/v2/keys/{key:.*}", v2.DeleteKeyHandler).Methods("DELETE")
+	s.handleFunc("/v2/leader", s.GetLeaderHandler).Methods("GET")
+	s.handleFunc("/v2/machines", s.GetMachinesHandler).Methods("GET")
+	s.handleFunc("/v2/stats/self", s.GetStatsHandler).Methods("GET")
+	s.handleFunc("/v2/stats/leader", s.GetLeaderStatsHandler).Methods("GET")
+	s.handleFunc("/v2/stats/store", s.GetStoreStatsHandler).Methods("GET")
+}
+
+// Adds a v1 server handler to the router.
+func (s *Server) handleFuncV1(path string, f func(http.ResponseWriter, *http.Request, v1.Server) error) *mux.Route {
+	return s.handleFunc(path, func(w http.ResponseWriter, req *http.Request) error {
+		return f(w, req, s)
+	})
+}
+
+// Adds a v2 server handler to the router.
+func (s *Server) handleFuncV2(path string, f func(http.ResponseWriter, *http.Request, v2.Server) error) *mux.Route {
+	return s.handleFunc(path, func(w http.ResponseWriter, req *http.Request) error {
+		return f(w, req, s)
+	})
+}
+
+// Adds a server handler to the router.
+func (s *Server) handleFunc(path string, f func(http.ResponseWriter, *http.Request) error) *mux.Route {
+	r := s.Handler.(*mux.Router)
+
+	// Wrap the standard HandleFunc interface to pass in the server reference.
+	return r.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
+		// Log request.
+		log.Debugf("[recv] %s %s %s [%s]", req.Method, s.url, req.URL.Path, req.RemoteAddr)
+
+		// Write CORS header.
+		if s.OriginAllowed("*") {
+			w.Header().Add("Access-Control-Allow-Origin", "*")
+		} else if origin := req.Header.Get("Origin"); s.OriginAllowed(origin) {
+			w.Header().Add("Access-Control-Allow-Origin", origin)
+		}
+
+		// Execute handler function and return error if necessary.
+		if err := f(w, req); err != nil {
+			if etcdErr, ok := err.(*etcdErr.Error); ok {
+				log.Debug("Return error: ", (*etcdErr).Error())
+				etcdErr.Write(w)
+			} else {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+			}
+		}
+	})
+}
+
+// Start to listen and response etcd client command
+func (s *Server) ListenAndServe() {
+	log.Infof("etcd server [name %s, listen on %s, advertised url %s]", s.name, s.Server.Addr, s.url)
+
+	if s.tlsConf.Scheme == "http" {
+		log.Fatal(s.Server.ListenAndServe())
+	} else {
+		log.Fatal(s.Server.ListenAndServeTLS(s.tlsInfo.CertFile, s.tlsInfo.KeyFile))
+	}
+}
+
+func (s *Server) Dispatch(c raft.Command, w http.ResponseWriter, req *http.Request) error {
+	if s.peerServer.State() == raft.Leader {
+		event, err := s.peerServer.Do(c)
+		if err != nil {
+			return err
+		}
+
+		if event == nil {
+			return etcdErr.NewError(300, "Empty result from raft", store.UndefIndex, store.UndefTerm)
+		}
+
+		if b, ok := event.([]byte); ok {
+			w.WriteHeader(http.StatusOK)
+			w.Write(b)
+		}
+
+		var b []byte
+		if strings.HasPrefix(req.URL.Path, "/v1") {
+			b, _ = json.Marshal(event.(*store.Event).Response())
+		} else {
+			b, _ = json.Marshal(event.(*store.Event))
+		}
+		w.WriteHeader(http.StatusOK)
+		w.Write(b)
+
+		return nil
+
+	} else {
+		leader := s.peerServer.Leader()
+
+		// No leader available.
+		if leader == "" {
+			return etcdErr.NewError(300, "", store.UndefIndex, store.UndefTerm)
+		}
+
+		var url string
+		switch c.(type) {
+		case *JoinCommand, *RemoveCommand:
+			url, _ = s.registry.PeerURL(leader)
+		default:
+			url, _ = s.registry.URL(leader)
+		}
+		redirect(url, w, req)
+
+		return nil
+	}
+}
+
+// Sets a comma-delimited list of origins that are allowed.
+func (s *Server) AllowOrigins(origins string) error {
+	// Construct a lookup of all origins.
+	m := make(map[string]bool)
+	for _, v := range strings.Split(origins, ",") {
+		if v != "*" {
+			if _, err := url.Parse(v); err != nil {
+				return fmt.Errorf("Invalid CORS origin: %s", err)
+			}
+		}
+		m[v] = true
+	}
+	s.corsOrigins = m
+
+	return nil
+}
+
+// Determines whether the server will allow a given CORS origin.
+func (s *Server) OriginAllowed(origin string) bool {
+	return s.corsOrigins["*"] || s.corsOrigins[origin]
+}
+
+// Handler to return the current version of etcd.
+func (s *Server) GetVersionHandler(w http.ResponseWriter, req *http.Request) error {
+	w.WriteHeader(http.StatusOK)
+	fmt.Fprintf(w, "etcd %s", releaseVersion)
+	return nil
+}
+
+// Handler to return the current leader's raft address
+func (s *Server) GetLeaderHandler(w http.ResponseWriter, req *http.Request) error {
+	leader := s.peerServer.Leader()
+	if leader == "" {
+		return etcdErr.NewError(etcdErr.EcodeLeaderElect, "", store.UndefIndex, store.UndefTerm)
+	}
+	w.WriteHeader(http.StatusOK)
+	url, _ := s.registry.PeerURL(leader)
+	w.Write([]byte(url))
+	return nil
+}
+
+// Handler to return all the known machines in the current cluster.
+func (s *Server) GetMachinesHandler(w http.ResponseWriter, req *http.Request) error {
+	machines := s.registry.URLs(s.peerServer.Leader(), s.name)
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte(strings.Join(machines, ", ")))
+	return nil
+}
+
+// Retrieves stats on the Raft server.
+func (s *Server) GetStatsHandler(w http.ResponseWriter, req *http.Request) error {
+	w.Write(s.peerServer.Stats())
+	return nil
+}
+
+// Retrieves stats on the leader.
+func (s *Server) GetLeaderStatsHandler(w http.ResponseWriter, req *http.Request) error {
+	if s.peerServer.State() == raft.Leader {
+		w.Write(s.peerServer.PeerStats())
+		return nil
+	}
+
+	leader := s.peerServer.Leader()
+	if leader == "" {
+		return etcdErr.NewError(300, "", store.UndefIndex, store.UndefTerm)
+	}
+	hostname, _ := s.registry.URL(leader)
+	redirect(hostname, w, req)
+	return nil
+}
+
+// Retrieves stats on the leader.
+func (s *Server) GetStoreStatsHandler(w http.ResponseWriter, req *http.Request) error {
+	w.Write(s.store.JsonStats())
+	return nil
+}
+
+// Executes a speed test to evaluate the performance of update replication.
+func (s *Server) SpeedTestHandler(w http.ResponseWriter, req *http.Request) error {
+	count := 1000
+	c := make(chan bool, count)
+	for i := 0; i < count; i++ {
+		go func() {
+			for j := 0; j < 10; j++ {
+				c := &store.UpdateCommand{
+					Key:        "foo",
+					Value:      "bar",
+					ExpireTime: time.Unix(0, 0),
+				}
+				s.peerServer.Do(c)
+			}
+			c <- true
+		}()
+	}
+
+	for i := 0; i < count; i++ {
+		<-c
+	}
+
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte("speed test success"))
+	return nil
+}

+ 89 - 0
server/stats_queue.go

@@ -0,0 +1,89 @@
+package server
+
+import (
+	"sync"
+	"time"
+)
+
+const (
+	queueCapacity = 200
+)
+
+type statsQueue struct {
+	items        [queueCapacity]*packageStats
+	size         int
+	front        int
+	back         int
+	totalPkgSize int
+	rwl          sync.RWMutex
+}
+
+func (q *statsQueue) Len() int {
+	return q.size
+}
+
+func (q *statsQueue) PkgSize() int {
+	return q.totalPkgSize
+}
+
+// FrontAndBack gets the front and back elements in the queue
+// We must grab front and back together with the protection of the lock
+func (q *statsQueue) frontAndBack() (*packageStats, *packageStats) {
+	q.rwl.RLock()
+	defer q.rwl.RUnlock()
+	if q.size != 0 {
+		return q.items[q.front], q.items[q.back]
+	}
+	return nil, nil
+}
+
+// Insert function insert a packageStats into the queue and update the records
+func (q *statsQueue) Insert(p *packageStats) {
+	q.rwl.Lock()
+	defer q.rwl.Unlock()
+
+	q.back = (q.back + 1) % queueCapacity
+
+	if q.size == queueCapacity { //dequeue
+		q.totalPkgSize -= q.items[q.front].size
+		q.front = (q.back + 1) % queueCapacity
+	} else {
+		q.size++
+	}
+
+	q.items[q.back] = p
+	q.totalPkgSize += q.items[q.back].size
+
+}
+
+// Rate function returns the package rate and byte rate
+func (q *statsQueue) Rate() (float64, float64) {
+	front, back := q.frontAndBack()
+
+	if front == nil || back == nil {
+		return 0, 0
+	}
+
+	if time.Now().Sub(back.Time()) > time.Second {
+		q.Clear()
+		return 0, 0
+	}
+
+	sampleDuration := back.Time().Sub(front.Time())
+
+	pr := float64(q.Len()) / float64(sampleDuration) * float64(time.Second)
+
+	br := float64(q.PkgSize()) / float64(sampleDuration) * float64(time.Second)
+
+	return pr, br
+}
+
+// Clear function clear up the statsQueue
+func (q *statsQueue) Clear() {
+	q.rwl.Lock()
+	defer q.rwl.Unlock()
+	q.back = -1
+	q.front = 0
+	q.size = 0
+	q.totalPkgSize = 0
+}

+ 15 - 0
server/timeout.go

@@ -0,0 +1,15 @@
+package server
+
+import (
+	"time"
+)
+
+const (
+	// The amount of time to elapse without a heartbeat before becoming a candidate.
+	ElectionTimeout = 200 * time.Millisecond
+
+	// The frequency by which heartbeats are sent to followers.
+	HeartbeatTimeout = 50 * time.Millisecond
+
+	RetryInterval = 10
+)

+ 11 - 0
server/tls_config.go

@@ -0,0 +1,11 @@
+package server
+
+import (
+	"crypto/tls"
+)
+
+type TLSConfig struct {
+	Scheme string
+	Server tls.Config
+	Client tls.Config
+}

+ 7 - 0
server/tls_info.go

@@ -0,0 +1,7 @@
+package server
+
+type TLSInfo struct {
+	CertFile string `json:"CertFile"`
+	KeyFile  string `json:"KeyFile"`
+	CAFile   string `json:"CAFile"`
+}

+ 21 - 27
transporter.go → server/transporter.go

@@ -1,4 +1,4 @@
-package main
+package server
 
 
 import (
 import (
 	"bytes"
 	"bytes"
@@ -10,6 +10,7 @@ import (
 	"net/http"
 	"net/http"
 	"time"
 	"time"
 
 
+	"github.com/coreos/etcd/log"
 	"github.com/coreos/go-raft"
 	"github.com/coreos/go-raft"
 )
 )
 
 
@@ -29,13 +30,13 @@ var tranTimeout = ElectionTimeout
 type transporter struct {
 type transporter struct {
 	client     *http.Client
 	client     *http.Client
 	transport  *http.Transport
 	transport  *http.Transport
-	raftServer *raftServer
+	peerServer *PeerServer
 }
 }
 
 
 // Create transporter using by raft server
 // Create transporter using by raft server
 // Create http or https transporter based on
 // Create http or https transporter based on
 // whether the user give the server cert and key
 // whether the user give the server cert and key
-func newTransporter(scheme string, tlsConf tls.Config, raftServer *raftServer) *transporter {
+func newTransporter(scheme string, tlsConf tls.Config, peerServer *PeerServer) *transporter {
 	t := transporter{}
 	t := transporter{}
 
 
 	tr := &http.Transport{
 	tr := &http.Transport{
@@ -50,7 +51,7 @@ func newTransporter(scheme string, tlsConf tls.Config, raftServer *raftServer) *
 
 
 	t.client = &http.Client{Transport: tr}
 	t.client = &http.Client{Transport: tr}
 	t.transport = tr
 	t.transport = tr
-	t.raftServer = raftServer
+	t.peerServer = peerServer
 
 
 	return &t
 	return &t
 }
 }
@@ -69,18 +70,18 @@ func (t *transporter) SendAppendEntriesRequest(server *raft.Server, peer *raft.P
 
 
 	size := b.Len()
 	size := b.Len()
 
 
-	t.raftServer.serverStats.SendAppendReq(size)
+	t.peerServer.serverStats.SendAppendReq(size)
 
 
-	u, _ := nameToRaftURL(peer.Name)
+	u, _ := t.peerServer.registry.PeerURL(peer.Name)
 
 
-	debugf("Send LogEntries to %s ", u)
+	log.Debugf("Send LogEntries to %s ", u)
 
 
-	thisFollowerStats, ok := t.raftServer.followersStats.Followers[peer.Name]
+	thisFollowerStats, ok := t.peerServer.followersStats.Followers[peer.Name]
 
 
 	if !ok { //this is the first time this follower has been seen
 	if !ok { //this is the first time this follower has been seen
 		thisFollowerStats = &raftFollowerStats{}
 		thisFollowerStats = &raftFollowerStats{}
 		thisFollowerStats.Latency.Minimum = 1 << 63
 		thisFollowerStats.Latency.Minimum = 1 << 63
-		t.raftServer.followersStats.Followers[peer.Name] = thisFollowerStats
+		t.peerServer.followersStats.Followers[peer.Name] = thisFollowerStats
 	}
 	}
 
 
 	start := time.Now()
 	start := time.Now()
@@ -90,7 +91,7 @@ func (t *transporter) SendAppendEntriesRequest(server *raft.Server, peer *raft.P
 	end := time.Now()
 	end := time.Now()
 
 
 	if err != nil {
 	if err != nil {
-		debugf("Cannot send AppendEntriesRequest to %s: %s", u, err)
+		log.Debugf("Cannot send AppendEntriesRequest to %s: %s", u, err)
 		if ok {
 		if ok {
 			thisFollowerStats.Fail()
 			thisFollowerStats.Fail()
 		}
 		}
@@ -121,13 +122,13 @@ func (t *transporter) SendVoteRequest(server *raft.Server, peer *raft.Peer, req
 	var b bytes.Buffer
 	var b bytes.Buffer
 	json.NewEncoder(&b).Encode(req)
 	json.NewEncoder(&b).Encode(req)
 
 
-	u, _ := nameToRaftURL(peer.Name)
-	debugf("Send Vote to %s", u)
+	u, _ := t.peerServer.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, httpRequest, err := t.Post(fmt.Sprintf("%s/vote", u), &b)
 
 
 	if err != nil {
 	if err != nil {
-		debugf("Cannot send VoteRequest to %s : %s", u, err)
+		log.Debugf("Cannot send VoteRequest to %s : %s", u, err)
 	}
 	}
 
 
 	if resp != nil {
 	if resp != nil {
@@ -150,14 +151,14 @@ func (t *transporter) SendSnapshotRequest(server *raft.Server, peer *raft.Peer,
 	var b bytes.Buffer
 	var b bytes.Buffer
 	json.NewEncoder(&b).Encode(req)
 	json.NewEncoder(&b).Encode(req)
 
 
-	u, _ := nameToRaftURL(peer.Name)
-	debugf("Send Snapshot to %s [Last Term: %d, LastIndex %d]", u,
+	u, _ := t.peerServer.registry.PeerURL(peer.Name)
+	log.Debugf("Send Snapshot to %s [Last Term: %d, LastIndex %d]", u,
 		req.LastTerm, req.LastIndex)
 		req.LastTerm, req.LastIndex)
 
 
 	resp, httpRequest, err := t.Post(fmt.Sprintf("%s/snapshot", u), &b)
 	resp, httpRequest, err := t.Post(fmt.Sprintf("%s/snapshot", u), &b)
 
 
 	if err != nil {
 	if err != nil {
-		debugf("Cannot send SendSnapshotRequest to %s : %s", u, err)
+		log.Debugf("Cannot send SendSnapshotRequest to %s : %s", u, err)
 	}
 	}
 
 
 	if resp != nil {
 	if resp != nil {
@@ -181,14 +182,14 @@ func (t *transporter) SendSnapshotRecoveryRequest(server *raft.Server, peer *raf
 	var b bytes.Buffer
 	var b bytes.Buffer
 	json.NewEncoder(&b).Encode(req)
 	json.NewEncoder(&b).Encode(req)
 
 
-	u, _ := nameToRaftURL(peer.Name)
-	debugf("Send SnapshotRecovery to %s [Last Term: %d, LastIndex %d]", u,
+	u, _ := t.peerServer.registry.PeerURL(peer.Name)
+	log.Debugf("Send SnapshotRecovery to %s [Last Term: %d, LastIndex %d]", u,
 		req.LastTerm, req.LastIndex)
 		req.LastTerm, req.LastIndex)
 
 
 	resp, _, err := t.Post(fmt.Sprintf("%s/snapshotRecovery", u), &b)
 	resp, _, err := t.Post(fmt.Sprintf("%s/snapshotRecovery", u), &b)
 
 
 	if err != nil {
 	if err != nil {
-		debugf("Cannot send SendSnapshotRecoveryRequest to %s : %s", u, err)
+		log.Debugf("Cannot send SendSnapshotRecoveryRequest to %s : %s", u, err)
 	}
 	}
 
 
 	if resp != nil {
 	if resp != nil {
@@ -205,26 +206,19 @@ func (t *transporter) SendSnapshotRecoveryRequest(server *raft.Server, peer *raf
 
 
 // Send server side POST request
 // Send server side POST request
 func (t *transporter) Post(urlStr string, body io.Reader) (*http.Response, *http.Request, error) {
 func (t *transporter) Post(urlStr string, body io.Reader) (*http.Response, *http.Request, error) {
-
 	req, _ := http.NewRequest("POST", urlStr, body)
 	req, _ := http.NewRequest("POST", urlStr, body)
-
 	resp, err := t.client.Do(req)
 	resp, err := t.client.Do(req)
-
 	return resp, req, err
 	return resp, req, err
-
 }
 }
 
 
 // Send server side GET request
 // Send server side GET request
 func (t *transporter) Get(urlStr string) (*http.Response, *http.Request, error) {
 func (t *transporter) Get(urlStr string) (*http.Response, *http.Request, error) {
-
 	req, _ := http.NewRequest("GET", urlStr, nil)
 	req, _ := http.NewRequest("GET", urlStr, nil)
-
 	resp, err := t.client.Do(req)
 	resp, err := t.client.Do(req)
-
 	return resp, req, err
 	return resp, req, err
 }
 }
 
 
-// Cancel the on fly HTTP transaction when timeout happens
+// Cancel the on fly HTTP transaction when timeout happens.
 func (t *transporter) CancelWhenTimeout(req *http.Request) {
 func (t *transporter) CancelWhenTimeout(req *http.Request) {
 	go func() {
 	go func() {
 		time.Sleep(ElectionTimeout)
 		time.Sleep(ElectionTimeout)

+ 1 - 1
transporter_test.go → server/transporter_test.go

@@ -1,4 +1,4 @@
-package main
+package server
 
 
 import (
 import (
 	"crypto/tls"
 	"crypto/tls"

+ 26 - 0
server/util.go

@@ -0,0 +1,26 @@
+package server
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+
+	"github.com/coreos/etcd/log"
+)
+
+func decodeJsonRequest(req *http.Request, data interface{}) error {
+	decoder := json.NewDecoder(req.Body)
+	if err := decoder.Decode(&data); err != nil && err != io.EOF {
+		log.Warnf("Malformed json request: %v", err)
+		return fmt.Errorf("Malformed json request: %v", err)
+	}
+	return nil
+}
+
+func redirect(hostname string, w http.ResponseWriter, req *http.Request) {
+	path := req.URL.Path
+	url := hostname + path
+	log.Debugf("Redirect to %s", url)
+	http.Redirect(w, req, url, http.StatusTemporaryRedirect)
+}

+ 15 - 0
server/v1/delete_key_handler.go

@@ -0,0 +1,15 @@
+package v1
+
+import (
+	"github.com/coreos/etcd/store"
+	"github.com/gorilla/mux"
+	"net/http"
+)
+
+// Removes a key from the store.
+func DeleteKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
+	vars := mux.Vars(req)
+	key := "/" + vars["key"]
+	c := &store.DeleteCommand{Key: key}
+	return s.Dispatch(c, w, req)
+}

+ 27 - 0
server/v1/get_key_handler.go

@@ -0,0 +1,27 @@
+package v1
+
+import (
+	"encoding/json"
+	"net/http"
+
+	"github.com/gorilla/mux"
+)
+
+// Retrieves the value for a given key.
+func GetKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
+	vars := mux.Vars(req)
+	key := "/" + vars["key"]
+
+	// Retrieve the key from the store.
+	event, err := s.Store().Get(key, false, false, s.CommitIndex(), s.Term())
+	if err != nil {
+		return err
+	}
+
+	// Convert event to a response and write to client.
+	b, _ := json.Marshal(event.Response())
+	w.WriteHeader(http.StatusOK)
+	w.Write(b)
+
+	return nil
+}

+ 51 - 0
server/v1/set_key_handler.go

@@ -0,0 +1,51 @@
+package v1
+
+import (
+	"net/http"
+
+	etcdErr "github.com/coreos/etcd/error"
+	"github.com/coreos/etcd/store"
+	"github.com/coreos/go-raft"
+	"github.com/gorilla/mux"
+)
+
+// Sets the value for a given key.
+func SetKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
+	vars := mux.Vars(req)
+	key := "/" + vars["key"]
+
+	req.ParseForm()
+
+	// Parse non-blank value.
+	value := req.Form.Get("value")
+	if len(value) == 0 {
+		return etcdErr.NewError(200, "Set", store.UndefIndex, store.UndefTerm)
+	}
+
+	// Convert time-to-live to an expiration time.
+	expireTime, err := store.TTL(req.Form.Get("ttl"))
+	if err != nil {
+		return etcdErr.NewError(202, "Set", store.UndefIndex, store.UndefTerm)
+	}
+
+	// If the "prevValue" is specified then test-and-set. Otherwise create a new key.
+	var c raft.Command
+	if prevValueArr, ok := req.Form["prevValue"]; ok && len(prevValueArr) > 0 {
+		c = &store.TestAndSetCommand{
+			Key:        key,
+			Value:      value,
+			PrevValue:  prevValueArr[0],
+			ExpireTime: expireTime,
+		}
+
+	} else {
+		c = &store.CreateCommand{
+			Key:        key,
+			Value:      value,
+			ExpireTime: expireTime,
+			Force:      true,
+		}
+	}
+
+	return s.Dispatch(c, w, req)
+}

+ 15 - 0
server/v1/v1.go

@@ -0,0 +1,15 @@
+package v1
+
+import (
+	"github.com/coreos/etcd/store"
+	"github.com/coreos/go-raft"
+	"net/http"
+)
+
+// The Server interface provides all the methods required for the v1 API.
+type Server interface {
+	CommitIndex() uint64
+	Term() uint64
+	Store() *store.Store
+	Dispatch(raft.Command, http.ResponseWriter, *http.Request) error
+}

+ 40 - 0
server/v1/watch_key_handler.go

@@ -0,0 +1,40 @@
+package v1
+
+import (
+	"encoding/json"
+	"net/http"
+	"strconv"
+
+	etcdErr "github.com/coreos/etcd/error"
+	"github.com/coreos/etcd/store"
+	"github.com/gorilla/mux"
+)
+
+// Watches a given key prefix for changes.
+func WatchKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
+	var err error
+	vars := mux.Vars(req)
+	key := "/" + vars["key"]
+
+	// Create a command to watch from a given index (default 0).
+	var sinceIndex uint64 = 0
+	if req.Method == "POST" {
+		sinceIndex, err = strconv.ParseUint(string(req.FormValue("index")), 10, 64)
+		if err != nil {
+			return etcdErr.NewError(203, "Watch From Index", store.UndefIndex, store.UndefTerm)
+		}
+	}
+
+	// Start the watcher on the store.
+	c, err := s.Store().Watch(key, false, sinceIndex, s.CommitIndex(), s.Term())
+	if err != nil {
+		return etcdErr.NewError(500, key, store.UndefIndex, store.UndefTerm)
+	}
+	event := <-c
+
+	b, _ := json.Marshal(event.Response())
+	w.WriteHeader(http.StatusOK)
+	w.Write(b)
+
+	return nil
+}

+ 29 - 0
server/v2/create_key_handler.go

@@ -0,0 +1,29 @@
+package v2
+
+import (
+	"net/http"
+
+	etcdErr "github.com/coreos/etcd/error"
+	"github.com/coreos/etcd/store"
+	"github.com/gorilla/mux"
+)
+
+func CreateKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
+	vars := mux.Vars(req)
+	key := "/" + vars["key"]
+
+	value := req.FormValue("value")
+	expireTime, err := store.TTL(req.FormValue("ttl"))
+	if err != nil {
+		return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Create", store.UndefIndex, store.UndefTerm)
+	}
+
+	c := &store.CreateCommand{
+		Key:               key,
+		Value:             value,
+		ExpireTime:        expireTime,
+		IncrementalSuffix: (req.FormValue("incremental") == "true"),
+	}
+
+	return s.Dispatch(c, w, req)
+}

+ 20 - 0
server/v2/delete_key_handler.go

@@ -0,0 +1,20 @@
+package v2
+
+import (
+	"net/http"
+
+	"github.com/coreos/etcd/store"
+	"github.com/gorilla/mux"
+)
+
+func DeleteKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
+	vars := mux.Vars(req)
+	key := "/" + vars["key"]
+
+	c := &store.DeleteCommand{
+		Key:       key,
+		Recursive: (req.FormValue("recursive") == "true"),
+	}
+
+	return s.Dispatch(c, w, req)
+}

+ 69 - 0
server/v2/get_key_handler.go

@@ -0,0 +1,69 @@
+package v2
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	etcdErr "github.com/coreos/etcd/error"
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/etcd/store"
+	"github.com/coreos/go-raft"
+	"github.com/gorilla/mux"
+)
+
+func GetKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
+	var err error
+	var event *store.Event
+
+	vars := mux.Vars(req)
+	key := "/" + vars["key"]
+
+	// Help client to redirect the request to the current leader
+	if req.FormValue("consistent") == "true" && s.State() != raft.Leader {
+		leader := s.Leader()
+		hostname, _ := s.PeerURL(leader)
+		url := hostname + req.URL.Path
+		log.Debugf("Redirect to %s", url)
+		http.Redirect(w, req, url, http.StatusTemporaryRedirect)
+		return nil
+	}
+
+	recursive := (req.FormValue("recursive") == "true")
+	sorted := (req.FormValue("sorted") == "true")
+
+	if req.FormValue("wait") == "true" { // watch
+		// Create a command to watch from a given index (default 0).
+		var sinceIndex uint64 = 0
+		if req.Method == "POST" {
+			sinceIndex, err = strconv.ParseUint(string(req.FormValue("wait_index")), 10, 64)
+			if err != nil {
+				return etcdErr.NewError(etcdErr.EcodeIndexNaN, "Watch From Index", store.UndefIndex, store.UndefTerm)
+			}
+		}
+
+		// Start the watcher on the store.
+		c, err := s.Store().Watch(key, recursive, sinceIndex, s.CommitIndex(), s.Term())
+		if err != nil {
+			return etcdErr.NewError(500, key, store.UndefIndex, store.UndefTerm)
+		}
+		event = <-c
+
+	} else { //get
+		// Retrieve the key from the store.
+		event, err = s.Store().Get(key, recursive, sorted, s.CommitIndex(), s.Term())
+		if err != nil {
+			return err
+		}
+	}
+
+	w.Header().Add("X-Etcd-Index", fmt.Sprint(event.Index))
+	w.Header().Add("X-Etcd-Term", fmt.Sprint(event.Term))
+	w.WriteHeader(http.StatusOK)
+
+	b, _ := json.Marshal(event)
+	w.Write(b)
+
+	return nil
+}

+ 64 - 0
server/v2/update_key_handler.go

@@ -0,0 +1,64 @@
+package v2
+
+import (
+	"net/http"
+	"strconv"
+
+	etcdErr "github.com/coreos/etcd/error"
+	"github.com/coreos/etcd/store"
+	"github.com/coreos/go-raft"
+	"github.com/gorilla/mux"
+)
+
+func UpdateKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
+	vars := mux.Vars(req)
+	key := "/" + vars["key"]
+
+	req.ParseForm()
+
+	value := req.Form.Get("value")
+	expireTime, err := store.TTL(req.Form.Get("ttl"))
+	if err != nil {
+		return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Update", store.UndefIndex, store.UndefTerm)
+	}
+
+	// Update should give at least one option
+	if value == "" && expireTime.Sub(store.Permanent) == 0 {
+		return etcdErr.NewError(etcdErr.EcodeValueOrTTLRequired, "Update", store.UndefIndex, store.UndefTerm)
+	}
+
+	prevValue, valueOk := req.Form["prevValue"]
+	prevIndexStr, indexOk := req.Form["prevIndex"]
+
+	var c raft.Command
+	if !valueOk && !indexOk { // update without test
+		c = &store.UpdateCommand{
+			Key:        key,
+			Value:      value,
+			ExpireTime: expireTime,
+		}
+
+	} else { // update with test
+		var prevIndex uint64
+
+		if indexOk {
+			prevIndex, err = strconv.ParseUint(prevIndexStr[0], 10, 64)
+
+			// bad previous index
+			if err != nil {
+				return etcdErr.NewError(etcdErr.EcodeIndexNaN, "Update", store.UndefIndex, store.UndefTerm)
+			}
+		} else {
+			prevIndex = 0
+		}
+
+		c = &store.TestAndSetCommand{
+			Key:       key,
+			Value:     value,
+			PrevValue: prevValue[0],
+			PrevIndex: prevIndex,
+		}
+	}
+
+	return s.Dispatch(c, w, req)
+}

+ 18 - 0
server/v2/v2.go

@@ -0,0 +1,18 @@
+package v2
+
+import (
+	"github.com/coreos/etcd/store"
+	"github.com/coreos/go-raft"
+	"net/http"
+)
+
+// The Server interface provides all the methods required for the v2 API.
+type Server interface {
+	State() string
+	Leader() string
+	CommitIndex() uint64
+	Term() uint64
+	PeerURL(string) (string, bool)
+	Store() *store.Store
+	Dispatch(raft.Command, http.ResponseWriter, *http.Request) error
+}

+ 3 - 3
version.go → server/version.go

@@ -1,8 +1,8 @@
-package main
+package server
 
 
-const version = "v2"
+const Version = "v2"
 
 
 // TODO: The release version (generated from the git tag) will be the raft
 // TODO: The release version (generated from the git tag) will be the raft
 // protocol version for now. When things settle down we will fix it like the
 // protocol version for now. When things settle down we will fix it like the
 // client API above.
 // client API above.
-const raftVersion = releaseVersion
+const PeerVersion = releaseVersion

+ 0 - 36
snapshot.go

@@ -1,36 +0,0 @@
-package main
-
-import (
-	"time"
-)
-
-// basic conf.
-// TODO: find a good policy to do snapshot
-type snapshotConf struct {
-	// Etcd will check if snapshot is need every checkingInterval
-	checkingInterval time.Duration
-	// The number of writes when the last snapshot happened
-	lastWrites uint64
-	// If the incremental number of writes since the last snapshot
-	// exceeds the write Threshold, etcd will do a snapshot
-	writesThr uint64
-}
-
-var snapConf *snapshotConf
-
-func (r *raftServer) newSnapshotConf() *snapshotConf {
-	// check snapshot every 3 seconds and the threshold is 20K
-	return &snapshotConf{time.Second * 3, 0, 20 * 1000}
-}
-
-func (r *raftServer) monitorSnapshot() {
-	for {
-		time.Sleep(snapConf.checkingInterval)
-		//currentWrites := etcdStore.TotalWrites() - snapConf.lastWrites
-		currentWrites := 0
-		if uint64(currentWrites) > snapConf.writesThr {
-			r.TakeSnapshot()
-			snapConf.lastWrites = 0
-		}
-	}
-}

+ 39 - 0
store/create_command.go

@@ -0,0 +1,39 @@
+package store
+
+import (
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/go-raft"
+	"time"
+)
+
+func init() {
+	raft.RegisterCommand(&CreateCommand{})
+}
+
+// Create command
+type CreateCommand struct {
+	Key               string    `json:"key"`
+	Value             string    `json:"value"`
+	ExpireTime        time.Time `json:"expireTime"`
+	IncrementalSuffix bool      `json:"incrementalSuffix"`
+	Force             bool      `json:"force"`
+}
+
+// The name of the create command in the log
+func (c *CreateCommand) CommandName() string {
+	return "etcd:create"
+}
+
+// Create node
+func (c *CreateCommand) Apply(server *raft.Server) (interface{}, error) {
+	s, _ := server.StateMachine().(*Store)
+
+	e, err := s.Create(c.Key, c.Value, c.IncrementalSuffix, c.Force, c.ExpireTime, server.CommitIndex(), server.Term())
+
+	if err != nil {
+		log.Debug(err)
+		return nil, err
+	}
+
+	return e, nil
+}

+ 35 - 0
store/delete_command.go

@@ -0,0 +1,35 @@
+package store
+
+import (
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/go-raft"
+)
+
+func init() {
+	raft.RegisterCommand(&DeleteCommand{})
+}
+
+// The DeleteCommand removes a key from the Store.
+type DeleteCommand struct {
+	Key       string `json:"key"`
+	Recursive bool   `json:"recursive"`
+}
+
+// The name of the delete command in the log
+func (c *DeleteCommand) CommandName() string {
+	return "etcd:delete"
+}
+
+// Delete the key
+func (c *DeleteCommand) Apply(server *raft.Server) (interface{}, error) {
+	s, _ := server.StateMachine().(*Store)
+
+	e, err := s.Delete(c.Key, c.Recursive, server.CommitIndex(), server.Term())
+
+	if err != nil {
+		log.Debug(err)
+		return nil, err
+	}
+
+	return e, nil
+}

+ 30 - 125
store/event.go

@@ -1,12 +1,7 @@
 package store
 package store
 
 
 import (
 import (
-	"fmt"
-	"strings"
-	"sync"
 	"time"
 	"time"
-
-	etcdErr "github.com/coreos/etcd/error"
 )
 )
 
 
 const (
 const (
@@ -46,129 +41,39 @@ func newEvent(action string, key string, index uint64, term uint64) *Event {
 	}
 	}
 }
 }
 
 
-type eventQueue struct {
-	Events   []*Event
-	Size     int
-	Front    int
-	Capacity int
-}
-
-func (eq *eventQueue) back() int {
-	return (eq.Front + eq.Size - 1 + eq.Capacity) % eq.Capacity
-}
-
-func (eq *eventQueue) insert(e *Event) {
-	index := (eq.back() + 1) % eq.Capacity
-
-	eq.Events[index] = e
-
-	if eq.Size == eq.Capacity { //dequeue
-		eq.Front = (index + 1) % eq.Capacity
-	} else {
-		eq.Size++
-	}
-
-}
-
-type EventHistory struct {
-	Queue      eventQueue
-	StartIndex uint64
-	LastIndex  uint64
-	LastTerm   uint64
-	DupCnt     uint64 // help to compute the watching point with duplicated indexes in the queue
-	rwl        sync.RWMutex
-}
-
-func newEventHistory(capacity int) *EventHistory {
-	return &EventHistory{
-		Queue: eventQueue{
-			Capacity: capacity,
-			Events:   make([]*Event, capacity),
-		},
-	}
-}
-
-// addEvent function adds event into the eventHistory
-func (eh *EventHistory) addEvent(e *Event) *Event {
-	eh.rwl.Lock()
-	defer eh.rwl.Unlock()
-
-	var duped uint64
-
-	if e.Index == UndefIndex {
-		e.Index = eh.LastIndex
-		e.Term = eh.LastTerm
-		duped = 1
-	}
-
-	eh.Queue.insert(e)
-
-	eh.LastIndex = e.Index
-	eh.LastTerm = e.Term
-	eh.DupCnt += duped
-
-	eh.StartIndex = eh.Queue.Events[eh.Queue.Front].Index
-
-	return e
-}
-
-// scan function is enumerating events from the index in history and
-// stops till the first point where the key has identified prefix
-func (eh *EventHistory) scan(prefix string, index uint64) (*Event, *etcdErr.Error) {
-	eh.rwl.RLock()
-	defer eh.rwl.RUnlock()
-
-	start := index - eh.StartIndex
-
-	// the index should locate after the event history's StartIndex
-	if start < 0 {
-		return nil,
-			etcdErr.NewError(etcdErr.EcodeEventIndexCleared,
-				fmt.Sprintf("the requested history has been cleared [%v/%v]",
-					eh.StartIndex, index), UndefIndex, UndefTerm)
-	}
-
-	// the index should locate before the size of the queue minus the duplicate count
-	if start >= (uint64(eh.Queue.Size) - eh.DupCnt) { // future index
-		return nil, nil
-	}
-
-	i := int((start + uint64(eh.Queue.Front)) % uint64(eh.Queue.Capacity))
-
-	for {
-		e := eh.Queue.Events[i]
-		if strings.HasPrefix(e.Key, prefix) && index <= e.Index { // make sure we bypass the smaller one
-			return e, nil
+// Converts an event object into a response object.
+func (event *Event) Response() interface{} {
+	if !event.Dir {
+		response := &Response{
+			Action:     event.Action,
+			Key:        event.Key,
+			Value:      event.Value,
+			PrevValue:  event.PrevValue,
+			Index:      event.Index,
+			TTL:        event.TTL,
+			Expiration: event.Expiration,
 		}
 		}
 
 
-		i = (i + 1) % eh.Queue.Capacity
-
-		if i == eh.Queue.back() { // find nothing, return and watch from current index
-			return nil, nil
+		if response.Action == Create || response.Action == Update {
+			response.Action = "set"
+			if response.PrevValue == "" {
+				response.NewKey = true
+			}
 		}
 		}
-	}
-}
-
-// clone will be protected by a stop-world lock
-// do not need to obtain internal lock
-func (eh *EventHistory) clone() *EventHistory {
-	clonedQueue := eventQueue{
-		Capacity: eh.Queue.Capacity,
-		Events:   make([]*Event, eh.Queue.Capacity),
-		Size:     eh.Queue.Size,
-		Front:    eh.Queue.Front,
-	}
 
 
-	for i, e := range eh.Queue.Events {
-		clonedQueue.Events[i] = e
-	}
-
-	return &EventHistory{
-		StartIndex: eh.StartIndex,
-		Queue:      clonedQueue,
-		LastIndex:  eh.LastIndex,
-		LastTerm:   eh.LastTerm,
-		DupCnt:     eh.DupCnt,
+		return response
+	} else {
+		responses := make([]*Response, len(event.KVPairs))
+
+		for i, kv := range event.KVPairs {
+			responses[i] = &Response{
+				Action: event.Action,
+				Key:    kv.Key,
+				Value:  kv.Value,
+				Dir:    kv.Dir,
+				Index:  event.Index,
+			}
+		}
+		return responses
 	}
 	}
-
 }
 }

+ 112 - 0
store/event_history.go

@@ -0,0 +1,112 @@
+package store
+
+import (
+	"fmt"
+	"strings"
+	"sync"
+
+	etcdErr "github.com/coreos/etcd/error"
+)
+
+type EventHistory struct {
+	Queue      eventQueue
+	StartIndex uint64
+	LastIndex  uint64
+	LastTerm   uint64
+	DupCnt     uint64 // help to compute the watching point with duplicated indexes in the queue
+	rwl        sync.RWMutex
+}
+
+func newEventHistory(capacity int) *EventHistory {
+	return &EventHistory{
+		Queue: eventQueue{
+			Capacity: capacity,
+			Events:   make([]*Event, capacity),
+		},
+	}
+}
+
+// addEvent function adds event into the eventHistory
+func (eh *EventHistory) addEvent(e *Event) *Event {
+	eh.rwl.Lock()
+	defer eh.rwl.Unlock()
+
+	var duped uint64
+
+	if e.Index == UndefIndex {
+		e.Index = eh.LastIndex
+		e.Term = eh.LastTerm
+		duped = 1
+	}
+
+	eh.Queue.insert(e)
+
+	eh.LastIndex = e.Index
+	eh.LastTerm = e.Term
+	eh.DupCnt += duped
+
+	eh.StartIndex = eh.Queue.Events[eh.Queue.Front].Index
+
+	return e
+}
+
+// scan function is enumerating events from the index in history and
+// stops till the first point where the key has identified prefix
+func (eh *EventHistory) scan(prefix string, index uint64) (*Event, *etcdErr.Error) {
+	eh.rwl.RLock()
+	defer eh.rwl.RUnlock()
+
+	start := index - eh.StartIndex
+
+	// the index should locate after the event history's StartIndex
+	if start < 0 {
+		return nil,
+			etcdErr.NewError(etcdErr.EcodeEventIndexCleared,
+				fmt.Sprintf("the requested history has been cleared [%v/%v]",
+					eh.StartIndex, index), UndefIndex, UndefTerm)
+	}
+
+	// the index should locate before the size of the queue minus the duplicate count
+	if start >= (uint64(eh.Queue.Size) - eh.DupCnt) { // future index
+		return nil, nil
+	}
+
+	i := int((start + uint64(eh.Queue.Front)) % uint64(eh.Queue.Capacity))
+
+	for {
+		e := eh.Queue.Events[i]
+		if strings.HasPrefix(e.Key, prefix) && index <= e.Index { // make sure we bypass the smaller one
+			return e, nil
+		}
+
+		i = (i + 1) % eh.Queue.Capacity
+
+		if i == eh.Queue.back() { // find nothing, return and watch from current index
+			return nil, nil
+		}
+	}
+}
+
+// clone will be protected by a stop-world lock
+// do not need to obtain internal lock
+func (eh *EventHistory) clone() *EventHistory {
+	clonedQueue := eventQueue{
+		Capacity: eh.Queue.Capacity,
+		Events:   make([]*Event, eh.Queue.Capacity),
+		Size:     eh.Queue.Size,
+		Front:    eh.Queue.Front,
+	}
+
+	for i, e := range eh.Queue.Events {
+		clonedQueue.Events[i] = e
+	}
+
+	return &EventHistory{
+		StartIndex: eh.StartIndex,
+		Queue:      clonedQueue,
+		LastIndex:  eh.LastIndex,
+		LastTerm:   eh.LastTerm,
+		DupCnt:     eh.DupCnt,
+	}
+
+}

+ 25 - 0
store/event_queue.go

@@ -0,0 +1,25 @@
+package store
+
+type eventQueue struct {
+	Events   []*Event
+	Size     int
+	Front    int
+	Capacity int
+}
+
+func (eq *eventQueue) back() int {
+	return (eq.Front + eq.Size - 1 + eq.Capacity) % eq.Capacity
+}
+
+func (eq *eventQueue) insert(e *Event) {
+	index := (eq.back() + 1) % eq.Capacity
+
+	eq.Events[index] = e
+
+	if eq.Size == eq.Capacity { //dequeue
+		eq.Front = (index + 1) % eq.Capacity
+	} else {
+		eq.Size++
+	}
+
+}

+ 41 - 0
store/test_and_set_command.go

@@ -0,0 +1,41 @@
+package store
+
+import (
+	"time"
+
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/go-raft"
+)
+
+func init() {
+	raft.RegisterCommand(&TestAndSetCommand{})
+}
+
+// The TestAndSetCommand performs a conditional update on a key in the store.
+type TestAndSetCommand struct {
+	Key        string    `json:"key"`
+	Value      string    `json:"value"`
+	ExpireTime time.Time `json:"expireTime"`
+	PrevValue  string    `json: prevValue`
+	PrevIndex  uint64    `json: prevIndex`
+}
+
+// The name of the testAndSet command in the log
+func (c *TestAndSetCommand) CommandName() string {
+	return "etcd:testAndSet"
+}
+
+// Set the key-value pair if the current value of the key equals to the given prevValue
+func (c *TestAndSetCommand) Apply(server *raft.Server) (interface{}, error) {
+	s, _ := server.StateMachine().(*Store)
+
+	e, err := s.TestAndSet(c.Key, c.PrevValue, c.PrevIndex,
+		c.Value, c.ExpireTime, server.CommitIndex(), server.Term())
+
+	if err != nil {
+		log.Debug(err)
+		return nil, err
+	}
+
+	return e, nil
+}

+ 20 - 0
store/ttl.go

@@ -0,0 +1,20 @@
+package store
+
+import (
+	"strconv"
+	"time"
+)
+
+// Convert string duration to time format
+func TTL(duration string) (time.Time, error) {
+	if duration != "" {
+		duration, err := strconv.Atoi(duration)
+		if err != nil {
+			return Permanent, err
+		}
+		return time.Now().Add(time.Second * (time.Duration)(duration)), nil
+
+	} else {
+		return Permanent, nil
+	}
+}

+ 38 - 0
store/update_command.go

@@ -0,0 +1,38 @@
+package store
+
+import (
+	"time"
+
+	"github.com/coreos/etcd/log"
+	"github.com/coreos/go-raft"
+)
+
+func init() {
+	raft.RegisterCommand(&UpdateCommand{})
+}
+
+// The UpdateCommand updates the value of a key in the Store.
+type UpdateCommand struct {
+	Key        string    `json:"key"`
+	Value      string    `json:"value"`
+	ExpireTime time.Time `json:"expireTime"`
+}
+
+// The name of the update command in the log
+func (c *UpdateCommand) CommandName() string {
+	return "etcd:update"
+}
+
+// Update node
+func (c *UpdateCommand) Apply(server *raft.Server) (interface{}, error) {
+	s, _ := server.StateMachine().(*Store)
+
+	e, err := s.Update(c.Key, c.Value, c.ExpireTime, server.CommitIndex(), server.Term())
+
+	if err != nil {
+		log.Debug(err)
+		return nil, err
+	}
+
+	return e, nil
+}

+ 2 - 0
third_party/deps

@@ -3,6 +3,8 @@ packages="
 	github.com/coreos/go-etcd
 	github.com/coreos/go-etcd
 	github.com/coreos/go-log/log
 	github.com/coreos/go-log/log
 	github.com/coreos/go-systemd
 	github.com/coreos/go-systemd
+	github.com/gorilla/context
+	github.com/gorilla/mux
 	bitbucket.org/kardianos/osext
 	bitbucket.org/kardianos/osext
 	code.google.com/p/go.net
 	code.google.com/p/go.net
 	code.google.com/p/goprotobuf
 	code.google.com/p/goprotobuf

+ 0 - 1
third_party/github.com/coreos/go-etcd/etcd/client.go

@@ -136,7 +136,6 @@ func (c *Client) internalSyncCluster(machines []string) bool {
 			logger.Debugf("update.leader[%s,%s]", c.cluster.Leader, c.cluster.Machines[0])
 			logger.Debugf("update.leader[%s,%s]", c.cluster.Leader, c.cluster.Machines[0])
 			c.cluster.Leader = c.cluster.Machines[0]
 			c.cluster.Leader = c.cluster.Machines[0]
 
 
-			logger.Debug("sync.machines ", c.cluster.Machines)
 			return true
 			return true
 		}
 		}
 	}
 	}

+ 27 - 0
third_party/github.com/gorilla/context/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+	 * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+	 * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+	 * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

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

@@ -0,0 +1,6 @@
+context
+=======
+
+gorilla/context is a general purpose registry for global request variables.
+
+Read the full documentation here: http://www.gorillatoolkit.org/pkg/context

+ 112 - 0
third_party/github.com/gorilla/context/context.go

@@ -0,0 +1,112 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+	"net/http"
+	"sync"
+	"time"
+)
+
+var (
+	mutex sync.Mutex
+	data  = make(map[*http.Request]map[interface{}]interface{})
+	datat = make(map[*http.Request]int64)
+)
+
+// Set stores a value for a given key in a given request.
+func Set(r *http.Request, key, val interface{}) {
+	mutex.Lock()
+	defer mutex.Unlock()
+	if data[r] == nil {
+		data[r] = make(map[interface{}]interface{})
+		datat[r] = time.Now().Unix()
+	}
+	data[r][key] = val
+}
+
+// Get returns a value stored for a given key in a given request.
+func Get(r *http.Request, key interface{}) interface{} {
+	mutex.Lock()
+	defer mutex.Unlock()
+	if data[r] != nil {
+		return data[r][key]
+	}
+	return nil
+}
+
+// GetOk returns stored value and presence state like multi-value return of map access.
+func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
+	mutex.Lock()
+	defer mutex.Unlock()
+	if _, ok := data[r]; ok {
+		value, ok := data[r][key]
+		return value, ok
+	}
+	return nil, false
+}
+
+// Delete removes a value stored for a given key in a given request.
+func Delete(r *http.Request, key interface{}) {
+	mutex.Lock()
+	defer mutex.Unlock()
+	if data[r] != nil {
+		delete(data[r], key)
+	}
+}
+
+// Clear removes all values stored for a given request.
+//
+// This is usually called by a handler wrapper to clean up request
+// variables at the end of a request lifetime. See ClearHandler().
+func Clear(r *http.Request) {
+	mutex.Lock()
+	defer mutex.Unlock()
+	clear(r)
+}
+
+// clear is Clear without the lock.
+func clear(r *http.Request) {
+	delete(data, r)
+	delete(datat, r)
+}
+
+// Purge removes request data stored for longer than maxAge, in seconds.
+// It returns the amount of requests removed.
+//
+// If maxAge <= 0, all request data is removed.
+//
+// This is only used for sanity check: in case context cleaning was not
+// properly set some request data can be kept forever, consuming an increasing
+// amount of memory. In case this is detected, Purge() must be called
+// periodically until the problem is fixed.
+func Purge(maxAge int) int {
+	mutex.Lock()
+	defer mutex.Unlock()
+	count := 0
+	if maxAge <= 0 {
+		count = len(data)
+		data = make(map[*http.Request]map[interface{}]interface{})
+		datat = make(map[*http.Request]int64)
+	} else {
+		min := time.Now().Unix() - int64(maxAge)
+		for r, _ := range data {
+			if datat[r] < min {
+				clear(r)
+				count++
+			}
+		}
+	}
+	return count
+}
+
+// ClearHandler wraps an http.Handler and clears request values at the end
+// of a request lifetime.
+func ClearHandler(h http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		defer Clear(r)
+		h.ServeHTTP(w, r)
+	})
+}

+ 66 - 0
third_party/github.com/gorilla/context/context_test.go

@@ -0,0 +1,66 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+	"net/http"
+	"testing"
+)
+
+type keyType int
+
+const (
+	key1 keyType = iota
+	key2
+)
+
+func TestContext(t *testing.T) {
+	assertEqual := func(val interface{}, exp interface{}) {
+		if val != exp {
+			t.Errorf("Expected %v, got %v.", exp, val)
+		}
+	}
+
+	r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+
+	// Get()
+	assertEqual(Get(r, key1), nil)
+
+	// Set()
+	Set(r, key1, "1")
+	assertEqual(Get(r, key1), "1")
+	assertEqual(len(data[r]), 1)
+
+	Set(r, key2, "2")
+	assertEqual(Get(r, key2), "2")
+	assertEqual(len(data[r]), 2)
+
+	//GetOk
+	value, ok := GetOk(r, key1)
+	assertEqual(value, "1")
+	assertEqual(ok, true)
+
+	value, ok = GetOk(r, "not exists")
+	assertEqual(value, nil)
+	assertEqual(ok, false)
+
+	Set(r, "nil value", nil)
+	value, ok = GetOk(r, "nil value")
+	assertEqual(value, nil)
+	assertEqual(ok, true)
+
+	// Delete()
+	Delete(r, key1)
+	assertEqual(Get(r, key1), nil)
+	assertEqual(len(data[r]), 2)
+
+	Delete(r, key2)
+	assertEqual(Get(r, key2), nil)
+	assertEqual(len(data[r]), 1)
+
+	// Clear()
+	Clear(r)
+	assertEqual(len(data), 0)
+}

+ 82 - 0
third_party/github.com/gorilla/context/doc.go

@@ -0,0 +1,82 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package gorilla/context stores values shared during a request lifetime.
+
+For example, a router can set variables extracted from the URL and later
+application handlers can access those values, or it can be used to store
+sessions values to be saved at the end of a request. There are several
+others common uses.
+
+The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
+
+	http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
+
+Here's the basic usage: first define the keys that you will need. The key
+type is interface{} so a key can be of any type that supports equality.
+Here we define a key using a custom int type to avoid name collisions:
+
+	package foo
+
+	import (
+		"github.com/gorilla/context"
+	)
+
+	type key int
+
+	const MyKey key = 0
+
+Then set a variable. Variables are bound to an http.Request object, so you
+need a request instance to set a value:
+
+	context.Set(r, MyKey, "bar")
+
+The application can later access the variable using the same key you provided:
+
+	func MyHandler(w http.ResponseWriter, r *http.Request) {
+		// val is "bar".
+		val := context.Get(r, foo.MyKey)
+
+		// returns ("bar", true)
+		val, ok := context.GetOk(r, foo.MyKey)
+		// ...
+	}
+
+And that's all about the basic usage. We discuss some other ideas below.
+
+Any type can be stored in the context. To enforce a given type, make the key
+private and wrap Get() and Set() to accept and return values of a specific
+type:
+
+	type key int
+
+	const mykey key = 0
+
+	// GetMyKey returns a value for this package from the request values.
+	func GetMyKey(r *http.Request) SomeType {
+		if rv := context.Get(r, mykey); rv != nil {
+			return rv.(SomeType)
+		}
+		return nil
+	}
+
+	// SetMyKey sets a value for this package in the request values.
+	func SetMyKey(r *http.Request, val SomeType) {
+		context.Set(r, mykey, val)
+	}
+
+Variables must be cleared at the end of a request, to remove all values
+that were stored. This can be done in an http.Handler, after a request was
+served. Just call Clear() passing the request:
+
+	context.Clear(r)
+
+...or use ClearHandler(), which conveniently wraps an http.Handler to clear
+variables at the end of a request lifetime.
+
+The Routers from the packages gorilla/mux and gorilla/pat call Clear()
+so if you are using either of them you don't need to clear the context manually.
+*/
+package context

+ 27 - 0
third_party/github.com/gorilla/mux/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+	 * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+	 * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+	 * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

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

@@ -0,0 +1,6 @@
+mux
+===
+
+gorilla/mux is a powerful URL router and dispatcher.
+
+Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux

+ 21 - 0
third_party/github.com/gorilla/mux/bench_test.go

@@ -0,0 +1,21 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"net/http"
+	"testing"
+)
+
+func BenchmarkMux(b *testing.B) {
+	router := new(Router)
+	handler := func(w http.ResponseWriter, r *http.Request) {}
+	router.HandleFunc("/v1/{v1}", handler)
+
+	request, _ := http.NewRequest("GET", "/v1/anything", nil)
+	for i := 0; i < b.N; i++ {
+		router.ServeHTTP(nil, request)
+	}
+}

+ 199 - 0
third_party/github.com/gorilla/mux/doc.go

@@ -0,0 +1,199 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package gorilla/mux implements a request router and dispatcher.
+
+The name mux stands for "HTTP request multiplexer". Like the standard
+http.ServeMux, mux.Router matches incoming requests against a list of
+registered routes and calls a handler for the route that matches the URL
+or other conditions. The main features are:
+
+	* Requests can be matched based on URL host, path, path prefix, schemes,
+	  header and query values, HTTP methods or using custom matchers.
+	* URL hosts and paths can have variables with an optional regular
+	  expression.
+	* Registered URLs can be built, or "reversed", which helps maintaining
+	  references to resources.
+	* Routes can be used as subrouters: nested routes are only tested if the
+	  parent route matches. This is useful to define groups of routes that
+	  share common conditions like a host, a path prefix or other repeated
+	  attributes. As a bonus, this optimizes request matching.
+	* It implements the http.Handler interface so it is compatible with the
+	  standard http.ServeMux.
+
+Let's start registering a couple of URL paths and handlers:
+
+	func main() {
+		r := mux.NewRouter()
+		r.HandleFunc("/", HomeHandler)
+		r.HandleFunc("/products", ProductsHandler)
+		r.HandleFunc("/articles", ArticlesHandler)
+		http.Handle("/", r)
+	}
+
+Here we register three routes mapping URL paths to handlers. This is
+equivalent to how http.HandleFunc() works: if an incoming request URL matches
+one of the paths, the corresponding handler is called passing
+(http.ResponseWriter, *http.Request) as parameters.
+
+Paths can have variables. They are defined using the format {name} or
+{name:pattern}. If a regular expression pattern is not defined, the matched
+variable will be anything until the next slash. For example:
+
+	r := mux.NewRouter()
+	r.HandleFunc("/products/{key}", ProductHandler)
+	r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
+	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
+
+The names are used to create a map of route variables which can be retrieved
+calling mux.Vars():
+
+	vars := mux.Vars(request)
+	category := vars["category"]
+
+And this is all you need to know about the basic usage. More advanced options
+are explained below.
+
+Routes can also be restricted to a domain or subdomain. Just define a host
+pattern to be matched. They can also have variables:
+
+	r := mux.NewRouter()
+	// Only matches if domain is "www.domain.com".
+	r.Host("www.domain.com")
+	// Matches a dynamic subdomain.
+	r.Host("{subdomain:[a-z]+}.domain.com")
+
+There are several other matchers that can be added. To match path prefixes:
+
+	r.PathPrefix("/products/")
+
+...or HTTP methods:
+
+	r.Methods("GET", "POST")
+
+...or URL schemes:
+
+	r.Schemes("https")
+
+...or header values:
+
+	r.Headers("X-Requested-With", "XMLHttpRequest")
+
+...or query values:
+
+	r.Queries("key", "value")
+
+...or to use a custom matcher function:
+
+	r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
+		return r.ProtoMajor == 0
+    })
+
+...and finally, it is possible to combine several matchers in a single route:
+
+	r.HandleFunc("/products", ProductsHandler).
+	  Host("www.domain.com").
+	  Methods("GET").
+	  Schemes("http")
+
+Setting the same matching conditions again and again can be boring, so we have
+a way to group several routes that share the same requirements.
+We call it "subrouting".
+
+For example, let's say we have several URLs that should only match when the
+host is "www.domain.com". Create a route for that host and get a "subrouter"
+from it:
+
+	r := mux.NewRouter()
+	s := r.Host("www.domain.com").Subrouter()
+
+Then register routes in the subrouter:
+
+	s.HandleFunc("/products/", ProductsHandler)
+	s.HandleFunc("/products/{key}", ProductHandler)
+	s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
+
+The three URL paths we registered above will only be tested if the domain is
+"www.domain.com", because the subrouter is tested first. This is not
+only convenient, but also optimizes request matching. You can create
+subrouters combining any attribute matchers accepted by a route.
+
+Subrouters can be used to create domain or path "namespaces": you define
+subrouters in a central place and then parts of the app can register its
+paths relatively to a given subrouter.
+
+There's one more thing about subroutes. When a subrouter has a path prefix,
+the inner routes use it as base for their paths:
+
+	r := mux.NewRouter()
+	s := r.PathPrefix("/products").Subrouter()
+	// "/products/"
+	s.HandleFunc("/", ProductsHandler)
+	// "/products/{key}/"
+	s.HandleFunc("/{key}/", ProductHandler)
+	// "/products/{key}/details"
+	s.HandleFunc("/{key}/details"), ProductDetailsHandler)
+
+Now let's see how to build registered URLs.
+
+Routes can be named. All routes that define a name can have their URLs built,
+or "reversed". We define a name calling Name() on a route. For example:
+
+	r := mux.NewRouter()
+	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+	  Name("article")
+
+To build a URL, get the route and call the URL() method, passing a sequence of
+key/value pairs for the route variables. For the previous route, we would do:
+
+	url, err := r.Get("article").URL("category", "technology", "id", "42")
+
+...and the result will be a url.URL with the following path:
+
+	"/articles/technology/42"
+
+This also works for host variables:
+
+	r := mux.NewRouter()
+	r.Host("{subdomain}.domain.com").
+	  Path("/articles/{category}/{id:[0-9]+}").
+	  HandlerFunc(ArticleHandler).
+	  Name("article")
+
+	// url.String() will be "http://news.domain.com/articles/technology/42"
+	url, err := r.Get("article").URL("subdomain", "news",
+									 "category", "technology",
+									 "id", "42")
+
+All variables defined in the route are required, and their values must
+conform to the corresponding patterns. These requirements guarantee that a
+generated URL will always match a registered route -- the only exception is
+for explicitly defined "build-only" routes which never match.
+
+There's also a way to build only the URL host or path for a route:
+use the methods URLHost() or URLPath() instead. For the previous route,
+we would do:
+
+	// "http://news.domain.com/"
+	host, err := r.Get("article").URLHost("subdomain", "news")
+
+	// "/articles/technology/42"
+	path, err := r.Get("article").URLPath("category", "technology", "id", "42")
+
+And if you use subrouters, host and path defined separately can be built
+as well:
+
+	r := mux.NewRouter()
+	s := r.Host("{subdomain}.domain.com").Subrouter()
+	s.Path("/articles/{category}/{id:[0-9]+}").
+	  HandlerFunc(ArticleHandler).
+	  Name("article")
+
+	// "http://news.domain.com/articles/technology/42"
+	url, err := r.Get("article").URL("subdomain", "news",
+									 "category", "technology",
+									 "id", "42")
+*/
+package mux

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

@@ -0,0 +1,339 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"fmt"
+	"net/http"
+	"path"
+
+	"github.com/gorilla/context"
+)
+
+// NewRouter returns a new router instance.
+func NewRouter() *Router {
+	return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
+}
+
+// Router registers routes to be matched and dispatches a handler.
+//
+// It implements the http.Handler interface, so it can be registered to serve
+// requests:
+//
+//     var router = mux.NewRouter()
+//
+//     func main() {
+//         http.Handle("/", router)
+//     }
+//
+// Or, for Google App Engine, register it in a init() function:
+//
+//     func init() {
+//         http.Handle("/", router)
+//     }
+//
+// This will send all incoming requests to the router.
+type Router struct {
+	// Configurable Handler to be used when no route matches.
+	NotFoundHandler http.Handler
+	// Parent route, if this is a subrouter.
+	parent parentRoute
+	// Routes to be matched, in order.
+	routes []*Route
+	// Routes by name for URL building.
+	namedRoutes map[string]*Route
+	// See Router.StrictSlash(). This defines the flag for new routes.
+	strictSlash bool
+	// If true, do not clear the request context after handling the request
+	KeepContext bool
+}
+
+// Match matches registered routes against the request.
+func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
+	for _, route := range r.routes {
+		if route.Match(req, match) {
+			return true
+		}
+	}
+	return false
+}
+
+// ServeHTTP dispatches the handler registered in the matched route.
+//
+// When there is a match, the route variables can be retrieved calling
+// mux.Vars(request).
+func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	// Clean path to canonical form and redirect.
+	if p := cleanPath(req.URL.Path); p != req.URL.Path {
+		w.Header().Set("Location", p)
+		w.WriteHeader(http.StatusMovedPermanently)
+		return
+	}
+	var match RouteMatch
+	var handler http.Handler
+	if r.Match(req, &match) {
+		handler = match.Handler
+		setVars(req, match.Vars)
+		setCurrentRoute(req, match.Route)
+	}
+	if handler == nil {
+		if r.NotFoundHandler == nil {
+			r.NotFoundHandler = http.NotFoundHandler()
+		}
+		handler = r.NotFoundHandler
+	}
+	if !r.KeepContext {
+		defer context.Clear(req)
+	}
+	handler.ServeHTTP(w, req)
+}
+
+// Get returns a route registered with the given name.
+func (r *Router) Get(name string) *Route {
+	return r.getNamedRoutes()[name]
+}
+
+// GetRoute returns a route registered with the given name. This method
+// was renamed to Get() and remains here for backwards compatibility.
+func (r *Router) GetRoute(name string) *Route {
+	return r.getNamedRoutes()[name]
+}
+
+// StrictSlash defines the slash behavior for new routes.
+//
+// When true, if the route path is "/path/", accessing "/path" will redirect
+// to the former and vice versa.
+//
+// Special case: when a route sets a path prefix, strict slash is
+// automatically set to false for that route because the redirect behavior
+// can't be determined for prefixes.
+func (r *Router) StrictSlash(value bool) *Router {
+	r.strictSlash = value
+	return r
+}
+
+// ----------------------------------------------------------------------------
+// parentRoute
+// ----------------------------------------------------------------------------
+
+// getNamedRoutes returns the map where named routes are registered.
+func (r *Router) getNamedRoutes() map[string]*Route {
+	if r.namedRoutes == nil {
+		if r.parent != nil {
+			r.namedRoutes = r.parent.getNamedRoutes()
+		} else {
+			r.namedRoutes = make(map[string]*Route)
+		}
+	}
+	return r.namedRoutes
+}
+
+// getRegexpGroup returns regexp definitions from the parent route, if any.
+func (r *Router) getRegexpGroup() *routeRegexpGroup {
+	if r.parent != nil {
+		return r.parent.getRegexpGroup()
+	}
+	return nil
+}
+
+// ----------------------------------------------------------------------------
+// Route factories
+// ----------------------------------------------------------------------------
+
+// NewRoute registers an empty route.
+func (r *Router) NewRoute() *Route {
+	route := &Route{parent: r, strictSlash: r.strictSlash}
+	r.routes = append(r.routes, route)
+	return route
+}
+
+// Handle registers a new route with a matcher for the URL path.
+// See Route.Path() and Route.Handler().
+func (r *Router) Handle(path string, handler http.Handler) *Route {
+	return r.NewRoute().Path(path).Handler(handler)
+}
+
+// HandleFunc registers a new route with a matcher for the URL path.
+// See Route.Path() and Route.HandlerFunc().
+func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
+	*http.Request)) *Route {
+	return r.NewRoute().Path(path).HandlerFunc(f)
+}
+
+// Headers registers a new route with a matcher for request header values.
+// See Route.Headers().
+func (r *Router) Headers(pairs ...string) *Route {
+	return r.NewRoute().Headers(pairs...)
+}
+
+// Host registers a new route with a matcher for the URL host.
+// See Route.Host().
+func (r *Router) Host(tpl string) *Route {
+	return r.NewRoute().Host(tpl)
+}
+
+// MatcherFunc registers a new route with a custom matcher function.
+// See Route.MatcherFunc().
+func (r *Router) MatcherFunc(f MatcherFunc) *Route {
+	return r.NewRoute().MatcherFunc(f)
+}
+
+// Methods registers a new route with a matcher for HTTP methods.
+// See Route.Methods().
+func (r *Router) Methods(methods ...string) *Route {
+	return r.NewRoute().Methods(methods...)
+}
+
+// Path registers a new route with a matcher for the URL path.
+// See Route.Path().
+func (r *Router) Path(tpl string) *Route {
+	return r.NewRoute().Path(tpl)
+}
+
+// PathPrefix registers a new route with a matcher for the URL path prefix.
+// See Route.PathPrefix().
+func (r *Router) PathPrefix(tpl string) *Route {
+	return r.NewRoute().PathPrefix(tpl)
+}
+
+// Queries registers a new route with a matcher for URL query values.
+// See Route.Queries().
+func (r *Router) Queries(pairs ...string) *Route {
+	return r.NewRoute().Queries(pairs...)
+}
+
+// Schemes registers a new route with a matcher for URL schemes.
+// See Route.Schemes().
+func (r *Router) Schemes(schemes ...string) *Route {
+	return r.NewRoute().Schemes(schemes...)
+}
+
+// ----------------------------------------------------------------------------
+// Context
+// ----------------------------------------------------------------------------
+
+// RouteMatch stores information about a matched route.
+type RouteMatch struct {
+	Route   *Route
+	Handler http.Handler
+	Vars    map[string]string
+}
+
+type contextKey int
+
+const (
+	varsKey contextKey = iota
+	routeKey
+)
+
+// Vars returns the route variables for the current request, if any.
+func Vars(r *http.Request) map[string]string {
+	if rv := context.Get(r, varsKey); rv != nil {
+		return rv.(map[string]string)
+	}
+	return nil
+}
+
+// CurrentRoute returns the matched route for the current request, if any.
+func CurrentRoute(r *http.Request) *Route {
+	if rv := context.Get(r, routeKey); rv != nil {
+		return rv.(*Route)
+	}
+	return nil
+}
+
+func setVars(r *http.Request, val interface{}) {
+	context.Set(r, varsKey, val)
+}
+
+func setCurrentRoute(r *http.Request, val interface{}) {
+	context.Set(r, routeKey, val)
+}
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+// cleanPath returns the canonical path for p, eliminating . and .. elements.
+// Borrowed from the net/http package.
+func cleanPath(p string) string {
+	if p == "" {
+		return "/"
+	}
+	if p[0] != '/' {
+		p = "/" + p
+	}
+	np := path.Clean(p)
+	// path.Clean removes trailing slash except for root;
+	// put the trailing slash back if necessary.
+	if p[len(p)-1] == '/' && np != "/" {
+		np += "/"
+	}
+	return np
+}
+
+// uniqueVars returns an error if two slices contain duplicated strings.
+func uniqueVars(s1, s2 []string) error {
+	for _, v1 := range s1 {
+		for _, v2 := range s2 {
+			if v1 == v2 {
+				return fmt.Errorf("mux: duplicated route variable %q", v2)
+			}
+		}
+	}
+	return nil
+}
+
+// mapFromPairs converts variadic string parameters to a string map.
+func mapFromPairs(pairs ...string) (map[string]string, error) {
+	length := len(pairs)
+	if length%2 != 0 {
+		return nil, fmt.Errorf(
+			"mux: number of parameters must be multiple of 2, got %v", pairs)
+	}
+	m := make(map[string]string, length/2)
+	for i := 0; i < length; i += 2 {
+		m[pairs[i]] = pairs[i+1]
+	}
+	return m, nil
+}
+
+// matchInArray returns true if the given string value is in the array.
+func matchInArray(arr []string, value string) bool {
+	for _, v := range arr {
+		if v == value {
+			return true
+		}
+	}
+	return false
+}
+
+// matchMap returns true if the given key/value pairs exist in a given map.
+func matchMap(toCheck map[string]string, toMatch map[string][]string,
+	canonicalKey bool) bool {
+	for k, v := range toCheck {
+		// Check if key exists.
+		if canonicalKey {
+			k = http.CanonicalHeaderKey(k)
+		}
+		if values := toMatch[k]; values == nil {
+			return false
+		} else if v != "" {
+			// If value was defined as an empty string we only check that the
+			// key exists. Otherwise we also check for equality.
+			valueExists := false
+			for _, value := range values {
+				if v == value {
+					valueExists = true
+					break
+				}
+			}
+			if !valueExists {
+				return false
+			}
+		}
+	}
+	return true
+}

+ 755 - 0
third_party/github.com/gorilla/mux/mux_test.go

@@ -0,0 +1,755 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/gorilla/context"
+)
+
+type routeTest struct {
+	title       string            // title of the test
+	route       *Route            // the route being tested
+	request     *http.Request     // a request to test the route
+	vars        map[string]string // the expected vars of the match
+	host        string            // the expected host of the match
+	path        string            // the expected path of the match
+	shouldMatch bool              // whether the request is expected to match the route at all
+}
+
+func TestHost(t *testing.T) {
+	// newRequestHost a new request with a method, url, and host header
+	newRequestHost := func(method, url, host string) *http.Request {
+		req, err := http.NewRequest(method, url, nil)
+		if err != nil {
+			panic(err)
+		}
+		req.Host = host
+		return req
+	}
+
+	tests := []routeTest{
+		{
+			title:       "Host route match",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route, wrong host in request URL",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host route with port, match",
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc:1234",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route with port, wrong port in request URL",
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc:1234",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host route, match with host in request header",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route, wrong host in request header",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequestHost("GET", "/111/222/333", "aaa.222.ccc"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: false,
+		},
+		// BUG {new(Route).Host("aaa.bbb.ccc:1234"), newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"), map[string]string{}, "aaa.bbb.ccc:1234", "", true},
+		{
+			title:       "Host route with port, wrong host in request header",
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
+			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc:1234",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host route with pattern, match",
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "bbb"},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route with pattern, wrong host in request URL",
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "bbb"},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host route with multiple patterns, match",
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route with multiple patterns, wrong host in request URL",
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestPath(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "Path route, match",
+			route:       new(Route).Path("/111/222/333"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: true,
+		},
+		{
+			title:       "Path route, wrong path in request in request URL",
+			route:       new(Route).Path("/111/222/333"),
+			request:     newRequest("GET", "http://localhost/1/2/3"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: false,
+		},
+		{
+			title:       "Path route with pattern, match",
+			route:       new(Route).Path("/111/{v1:[0-9]{3}}/333"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{"v1": "222"},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: true,
+		},
+		{
+			title:       "Path route with pattern, URL in request does not match",
+			route:       new(Route).Path("/111/{v1:[0-9]{3}}/333"),
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:        map[string]string{"v1": "222"},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: false,
+		},
+		{
+			title:       "Path route with multiple patterns, match",
+			route:       new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{"v1": "111", "v2": "222", "v3": "333"},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: true,
+		},
+		{
+			title:       "Path route with multiple patterns, URL in request does not match",
+			route:       new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:        map[string]string{"v1": "111", "v2": "222", "v3": "333"},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestPathPrefix(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "PathPrefix route, match",
+			route:       new(Route).PathPrefix("/111"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111",
+			shouldMatch: true,
+		},
+		{
+			title:       "PathPrefix route, URL prefix in request does not match",
+			route:       new(Route).PathPrefix("/111"),
+			request:     newRequest("GET", "http://localhost/1/2/3"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111",
+			shouldMatch: false,
+		},
+		{
+			title:       "PathPrefix route with pattern, match",
+			route:       new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{"v1": "222"},
+			host:        "",
+			path:        "/111/222",
+			shouldMatch: true,
+		},
+		{
+			title:       "PathPrefix route with pattern, URL prefix in request does not match",
+			route:       new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:        map[string]string{"v1": "222"},
+			host:        "",
+			path:        "/111/222",
+			shouldMatch: false,
+		},
+		{
+			title:       "PathPrefix route with multiple patterns, match",
+			route:       new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{"v1": "111", "v2": "222"},
+			host:        "",
+			path:        "/111/222",
+			shouldMatch: true,
+		},
+		{
+			title:       "PathPrefix route with multiple patterns, URL prefix in request does not match",
+			route:       new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:        map[string]string{"v1": "111", "v2": "222"},
+			host:        "",
+			path:        "/111/222",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestHostPath(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "Host and Path route, match",
+			route:       new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host and Path route, wrong host in request URL",
+			route:       new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host and Path route with pattern, match",
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "bbb", "v2": "222"},
+			host:        "aaa.bbb.ccc",
+			path:        "/111/222/333",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host and Path route with pattern, URL in request does not match",
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "bbb", "v2": "222"},
+			host:        "aaa.bbb.ccc",
+			path:        "/111/222/333",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host and Path route with multiple patterns, match",
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
+			host:        "aaa.bbb.ccc",
+			path:        "/111/222/333",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host and Path route with multiple patterns, URL in request does not match",
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
+			host:        "aaa.bbb.ccc",
+			path:        "/111/222/333",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestHeaders(t *testing.T) {
+	// newRequestHeaders creates a new request with a method, url, and headers
+	newRequestHeaders := func(method, url string, headers map[string]string) *http.Request {
+		req, err := http.NewRequest(method, url, nil)
+		if err != nil {
+			panic(err)
+		}
+		for k, v := range headers {
+			req.Header.Add(k, v)
+		}
+		return req
+	}
+
+	tests := []routeTest{
+		{
+			title:       "Headers route, match",
+			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
+			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Headers route, bad header values",
+			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
+			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+
+}
+
+func TestMethods(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "Methods route, match GET",
+			route:       new(Route).Methods("GET", "POST"),
+			request:     newRequest("GET", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Methods route, match POST",
+			route:       new(Route).Methods("GET", "POST"),
+			request:     newRequest("POST", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Methods route, bad method",
+			route:       new(Route).Methods("GET", "POST"),
+			request:     newRequest("PUT", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestQueries(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "Queries route, match",
+			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
+			request:     newRequest("GET", "http://localhost?foo=bar&baz=ding"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route, bad query",
+			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
+			request:     newRequest("GET", "http://localhost?foo=bar&baz=dong"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestSchemes(t *testing.T) {
+	tests := []routeTest{
+		// Schemes
+		{
+			title:       "Schemes route, match https",
+			route:       new(Route).Schemes("https", "ftp"),
+			request:     newRequest("GET", "https://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Schemes route, match ftp",
+			route:       new(Route).Schemes("https", "ftp"),
+			request:     newRequest("GET", "ftp://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Schemes route, bad scheme",
+			route:       new(Route).Schemes("https", "ftp"),
+			request:     newRequest("GET", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestMatcherFunc(t *testing.T) {
+	m := func(r *http.Request, m *RouteMatch) bool {
+		if r.URL.Host == "aaa.bbb.ccc" {
+			return true
+		}
+		return false
+	}
+
+	tests := []routeTest{
+		{
+			title:       "MatchFunc route, match",
+			route:       new(Route).MatcherFunc(m),
+			request:     newRequest("GET", "http://aaa.bbb.ccc"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "MatchFunc route, non-match",
+			route:       new(Route).MatcherFunc(m),
+			request:     newRequest("GET", "http://aaa.222.ccc"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestSubRouter(t *testing.T) {
+	subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
+	subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()
+
+	tests := []routeTest{
+		{
+			route:       subrouter1.Path("/{v2:[a-z]+}"),
+			request:     newRequest("GET", "http://aaa.google.com/bbb"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb"},
+			host:        "aaa.google.com",
+			path:        "/bbb",
+			shouldMatch: true,
+		},
+		{
+			route:       subrouter1.Path("/{v2:[a-z]+}"),
+			request:     newRequest("GET", "http://111.google.com/111"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb"},
+			host:        "aaa.google.com",
+			path:        "/bbb",
+			shouldMatch: false,
+		},
+		{
+			route:       subrouter2.Path("/baz/{v2}"),
+			request:     newRequest("GET", "http://localhost/foo/bar/baz/ding"),
+			vars:        map[string]string{"v1": "bar", "v2": "ding"},
+			host:        "",
+			path:        "/foo/bar/baz/ding",
+			shouldMatch: true,
+		},
+		{
+			route:       subrouter2.Path("/baz/{v2}"),
+			request:     newRequest("GET", "http://localhost/foo/bar"),
+			vars:        map[string]string{"v1": "bar", "v2": "ding"},
+			host:        "",
+			path:        "/foo/bar/baz/ding",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestNamedRoutes(t *testing.T) {
+	r1 := NewRouter()
+	r1.NewRoute().Name("a")
+	r1.NewRoute().Name("b")
+	r1.NewRoute().Name("c")
+
+	r2 := r1.NewRoute().Subrouter()
+	r2.NewRoute().Name("d")
+	r2.NewRoute().Name("e")
+	r2.NewRoute().Name("f")
+
+	r3 := r2.NewRoute().Subrouter()
+	r3.NewRoute().Name("g")
+	r3.NewRoute().Name("h")
+	r3.NewRoute().Name("i")
+
+	if r1.namedRoutes == nil || len(r1.namedRoutes) != 9 {
+		t.Errorf("Expected 9 named routes, got %v", r1.namedRoutes)
+	} else if r1.Get("i") == nil {
+		t.Errorf("Subroute name not registered")
+	}
+}
+
+func TestStrictSlash(t *testing.T) {
+	var r *Router
+	var req *http.Request
+	var route *Route
+	var match *RouteMatch
+	var matched bool
+
+	// StrictSlash should be ignored for path prefix.
+	// So we register a route ending in slash but it doesn't attempt to add
+	// the slash for a path not ending in slash.
+	r = NewRouter()
+	r.StrictSlash(true)
+	route = r.NewRoute().PathPrefix("/static/")
+	req, _ = http.NewRequest("GET", "http://localhost/static/logo.png", nil)
+	match = new(RouteMatch)
+	matched = r.Match(req, match)
+	if !matched {
+		t.Errorf("Should match request %q -- %v", req.URL.Path, getRouteTemplate(route))
+	}
+	if match.Handler != nil {
+		t.Errorf("Should not redirect")
+	}
+}
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+func getRouteTemplate(route *Route) string {
+	host, path := "none", "none"
+	if route.regexp != nil {
+		if route.regexp.host != nil {
+			host = route.regexp.host.template
+		}
+		if route.regexp.path != nil {
+			path = route.regexp.path.template
+		}
+	}
+	return fmt.Sprintf("Host: %v, Path: %v", host, path)
+}
+
+func testRoute(t *testing.T, test routeTest) {
+	request := test.request
+	route := test.route
+	vars := test.vars
+	shouldMatch := test.shouldMatch
+	host := test.host
+	path := test.path
+	url := test.host + test.path
+
+	var match RouteMatch
+	ok := route.Match(request, &match)
+	if ok != shouldMatch {
+		msg := "Should match"
+		if !shouldMatch {
+			msg = "Should not match"
+		}
+		t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars)
+		return
+	}
+	if shouldMatch {
+		if test.vars != nil && !stringMapEqual(test.vars, match.Vars) {
+			t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
+			return
+		}
+		if host != "" {
+			u, _ := test.route.URLHost(mapToPairs(match.Vars)...)
+			if host != u.Host {
+				t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route))
+				return
+			}
+		}
+		if path != "" {
+			u, _ := route.URLPath(mapToPairs(match.Vars)...)
+			if path != u.Path {
+				t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route))
+				return
+			}
+		}
+		if url != "" {
+			u, _ := route.URL(mapToPairs(match.Vars)...)
+			if url != u.Host+u.Path {
+				t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route))
+				return
+			}
+		}
+	}
+}
+
+// Tests that the context is cleared or not cleared properly depending on
+// the configuration of the router
+func TestKeepContext(t *testing.T) {
+	func1 := func(w http.ResponseWriter, r *http.Request) {}
+
+	r := NewRouter()
+	r.HandleFunc("/", func1).Name("func1")
+
+	req, _ := http.NewRequest("GET", "http://localhost/", nil)
+	context.Set(req, "t", 1)
+
+	res := new(http.ResponseWriter)
+	r.ServeHTTP(*res, req)
+
+	if _, ok := context.GetOk(req, "t"); ok {
+		t.Error("Context should have been cleared at end of request")
+	}
+
+	r.KeepContext = true
+
+	req, _ = http.NewRequest("GET", "http://localhost/", nil)
+	context.Set(req, "t", 1)
+
+	r.ServeHTTP(*res, req)
+	if _, ok := context.GetOk(req, "t"); !ok {
+		t.Error("Context should NOT have been cleared at end of request")
+	}
+
+}
+
+// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
+func TestSubrouterHeader(t *testing.T) {
+	expected := "func1 response"
+	func1 := func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprint(w, expected)
+	}
+	func2 := func(http.ResponseWriter, *http.Request) {}
+
+	r := NewRouter()
+	s := r.Headers("SomeSpecialHeader", "").Subrouter()
+	s.HandleFunc("/", func1).Name("func1")
+	r.HandleFunc("/", func2).Name("func2")
+
+	req, _ := http.NewRequest("GET", "http://localhost/", nil)
+	req.Header.Add("SomeSpecialHeader", "foo")
+	match := new(RouteMatch)
+	matched := r.Match(req, match)
+	if !matched {
+		t.Errorf("Should match request")
+	}
+	if match.Route.GetName() != "func1" {
+		t.Errorf("Expecting func1 handler, got %s", match.Route.GetName())
+	}
+	resp := NewRecorder()
+	match.Handler.ServeHTTP(resp, req)
+	if resp.Body.String() != expected {
+		t.Errorf("Expecting %q", expected)
+	}
+}
+
+// mapToPairs converts a string map to a slice of string pairs
+func mapToPairs(m map[string]string) []string {
+	var i int
+	p := make([]string, len(m)*2)
+	for k, v := range m {
+		p[i] = k
+		p[i+1] = v
+		i += 2
+	}
+	return p
+}
+
+// stringMapEqual checks the equality of two string maps
+func stringMapEqual(m1, m2 map[string]string) bool {
+	nil1 := m1 == nil
+	nil2 := m2 == nil
+	if nil1 != nil2 || len(m1) != len(m2) {
+		return false
+	}
+	for k, v := range m1 {
+		if v != m2[k] {
+			return false
+		}
+	}
+	return true
+}
+
+// newRequest is a helper function to create a new request with a method and url
+func newRequest(method, url string) *http.Request {
+	req, err := http.NewRequest(method, url, nil)
+	if err != nil {
+		panic(err)
+	}
+	return req
+}

+ 758 - 0
third_party/github.com/gorilla/mux/old_test.go

@@ -0,0 +1,758 @@
+// Old tests ported to Go1. This is a mess. Want to drop it one day.
+
+// Copyright 2011 Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"bytes"
+	"net/http"
+	"testing"
+)
+
+// ----------------------------------------------------------------------------
+// ResponseRecorder
+// ----------------------------------------------------------------------------
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// ResponseRecorder is an implementation of http.ResponseWriter that
+// records its mutations for later inspection in tests.
+type ResponseRecorder struct {
+	Code      int           // the HTTP response code from WriteHeader
+	HeaderMap http.Header   // the HTTP response headers
+	Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
+	Flushed   bool
+}
+
+// NewRecorder returns an initialized ResponseRecorder.
+func NewRecorder() *ResponseRecorder {
+	return &ResponseRecorder{
+		HeaderMap: make(http.Header),
+		Body:      new(bytes.Buffer),
+	}
+}
+
+// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
+// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
+const DefaultRemoteAddr = "1.2.3.4"
+
+// Header returns the response headers.
+func (rw *ResponseRecorder) Header() http.Header {
+	return rw.HeaderMap
+}
+
+// Write always succeeds and writes to rw.Body, if not nil.
+func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
+	if rw.Body != nil {
+		rw.Body.Write(buf)
+	}
+	if rw.Code == 0 {
+		rw.Code = http.StatusOK
+	}
+	return len(buf), nil
+}
+
+// WriteHeader sets rw.Code.
+func (rw *ResponseRecorder) WriteHeader(code int) {
+	rw.Code = code
+}
+
+// Flush sets rw.Flushed to true.
+func (rw *ResponseRecorder) Flush() {
+	rw.Flushed = true
+}
+
+// ----------------------------------------------------------------------------
+
+func TestRouteMatchers(t *testing.T) {
+	var scheme, host, path, query, method string
+	var headers map[string]string
+	var resultVars map[bool]map[string]string
+
+	router := NewRouter()
+	router.NewRoute().Host("{var1}.google.com").
+		Path("/{var2:[a-z]+}/{var3:[0-9]+}").
+		Queries("foo", "bar").
+		Methods("GET").
+		Schemes("https").
+		Headers("x-requested-with", "XMLHttpRequest")
+	router.NewRoute().Host("www.{var4}.com").
+		PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
+		Queries("baz", "ding").
+		Methods("POST").
+		Schemes("http").
+		Headers("Content-Type", "application/json")
+
+	reset := func() {
+		// Everything match.
+		scheme = "https"
+		host = "www.google.com"
+		path = "/product/42"
+		query = "?foo=bar"
+		method = "GET"
+		headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
+		resultVars = map[bool]map[string]string{
+			true:  map[string]string{"var1": "www", "var2": "product", "var3": "42"},
+			false: map[string]string{},
+		}
+	}
+
+	reset2 := func() {
+		// Everything match.
+		scheme = "http"
+		host = "www.google.com"
+		path = "/foo/product/42/path/that/is/ignored"
+		query = "?baz=ding"
+		method = "POST"
+		headers = map[string]string{"Content-Type": "application/json"}
+		resultVars = map[bool]map[string]string{
+			true:  map[string]string{"var4": "google", "var5": "product", "var6": "42"},
+			false: map[string]string{},
+		}
+	}
+
+	match := func(shouldMatch bool) {
+		url := scheme + "://" + host + path + query
+		request, _ := http.NewRequest(method, url, nil)
+		for key, value := range headers {
+			request.Header.Add(key, value)
+		}
+
+		var routeMatch RouteMatch
+		matched := router.Match(request, &routeMatch)
+		if matched != shouldMatch {
+			// Need better messages. :)
+			if matched {
+				t.Errorf("Should match.")
+			} else {
+				t.Errorf("Should not match.")
+			}
+		}
+
+		if matched {
+			currentRoute := routeMatch.Route
+			if currentRoute == nil {
+				t.Errorf("Expected a current route.")
+			}
+			vars := routeMatch.Vars
+			expectedVars := resultVars[shouldMatch]
+			if len(vars) != len(expectedVars) {
+				t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
+			}
+			for name, value := range vars {
+				if expectedVars[name] != value {
+					t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
+				}
+			}
+		}
+	}
+
+	// 1st route --------------------------------------------------------------
+
+	// Everything match.
+	reset()
+	match(true)
+
+	// Scheme doesn't match.
+	reset()
+	scheme = "http"
+	match(false)
+
+	// Host doesn't match.
+	reset()
+	host = "www.mygoogle.com"
+	match(false)
+
+	// Path doesn't match.
+	reset()
+	path = "/product/notdigits"
+	match(false)
+
+	// Query doesn't match.
+	reset()
+	query = "?foo=baz"
+	match(false)
+
+	// Method doesn't match.
+	reset()
+	method = "POST"
+	match(false)
+
+	// Header doesn't match.
+	reset()
+	headers = map[string]string{}
+	match(false)
+
+	// Everything match, again.
+	reset()
+	match(true)
+
+	// 2nd route --------------------------------------------------------------
+
+	// Everything match.
+	reset2()
+	match(true)
+
+	// Scheme doesn't match.
+	reset2()
+	scheme = "https"
+	match(false)
+
+	// Host doesn't match.
+	reset2()
+	host = "sub.google.com"
+	match(false)
+
+	// Path doesn't match.
+	reset2()
+	path = "/bar/product/42"
+	match(false)
+
+	// Query doesn't match.
+	reset2()
+	query = "?foo=baz"
+	match(false)
+
+	// Method doesn't match.
+	reset2()
+	method = "GET"
+	match(false)
+
+	// Header doesn't match.
+	reset2()
+	headers = map[string]string{}
+	match(false)
+
+	// Everything match, again.
+	reset2()
+	match(true)
+}
+
+type headerMatcherTest struct {
+	matcher headerMatcher
+	headers map[string]string
+	result  bool
+}
+
+var headerMatcherTests = []headerMatcherTest{
+	{
+		matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
+		headers: map[string]string{"X-Requested-With": "XMLHttpRequest"},
+		result:  true,
+	},
+	{
+		matcher: headerMatcher(map[string]string{"x-requested-with": ""}),
+		headers: map[string]string{"X-Requested-With": "anything"},
+		result:  true,
+	},
+	{
+		matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
+		headers: map[string]string{},
+		result:  false,
+	},
+}
+
+type hostMatcherTest struct {
+	matcher *Route
+	url     string
+	vars    map[string]string
+	result  bool
+}
+
+var hostMatcherTests = []hostMatcherTest{
+	{
+		matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
+		url:     "http://abc.def.ghi/",
+		vars:    map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
+		result:  true,
+	},
+	{
+		matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
+		url:     "http://a.b.c/",
+		vars:    map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
+		result:  false,
+	},
+}
+
+type methodMatcherTest struct {
+	matcher methodMatcher
+	method  string
+	result  bool
+}
+
+var methodMatcherTests = []methodMatcherTest{
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "GET",
+		result:  true,
+	},
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "POST",
+		result:  true,
+	},
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "PUT",
+		result:  true,
+	},
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "DELETE",
+		result:  false,
+	},
+}
+
+type pathMatcherTest struct {
+	matcher *Route
+	url     string
+	vars    map[string]string
+	result  bool
+}
+
+var pathMatcherTests = []pathMatcherTest{
+	{
+		matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
+		url:     "http://localhost:8080/123/456/789",
+		vars:    map[string]string{"foo": "123", "bar": "456", "baz": "789"},
+		result:  true,
+	},
+	{
+		matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
+		url:     "http://localhost:8080/1/2/3",
+		vars:    map[string]string{"foo": "123", "bar": "456", "baz": "789"},
+		result:  false,
+	},
+}
+
+type queryMatcherTest struct {
+	matcher queryMatcher
+	url     string
+	result  bool
+}
+
+var queryMatcherTests = []queryMatcherTest{
+	{
+		matcher: queryMatcher(map[string]string{"foo": "bar", "baz": "ding"}),
+		url:     "http://localhost:8080/?foo=bar&baz=ding",
+		result:  true,
+	},
+	{
+		matcher: queryMatcher(map[string]string{"foo": "", "baz": ""}),
+		url:     "http://localhost:8080/?foo=anything&baz=anything",
+		result:  true,
+	},
+	{
+		matcher: queryMatcher(map[string]string{"foo": "ding", "baz": "bar"}),
+		url:     "http://localhost:8080/?foo=bar&baz=ding",
+		result:  false,
+	},
+	{
+		matcher: queryMatcher(map[string]string{"bar": "foo", "ding": "baz"}),
+		url:     "http://localhost:8080/?foo=bar&baz=ding",
+		result:  false,
+	},
+}
+
+type schemeMatcherTest struct {
+	matcher schemeMatcher
+	url     string
+	result  bool
+}
+
+var schemeMatcherTests = []schemeMatcherTest{
+	{
+		matcher: schemeMatcher([]string{"http", "https"}),
+		url:     "http://localhost:8080/",
+		result:  true,
+	},
+	{
+		matcher: schemeMatcher([]string{"http", "https"}),
+		url:     "https://localhost:8080/",
+		result:  true,
+	},
+	{
+		matcher: schemeMatcher([]string{"https"}),
+		url:     "http://localhost:8080/",
+		result:  false,
+	},
+	{
+		matcher: schemeMatcher([]string{"http"}),
+		url:     "https://localhost:8080/",
+		result:  false,
+	},
+}
+
+type urlBuildingTest struct {
+	route *Route
+	vars  []string
+	url   string
+}
+
+var urlBuildingTests = []urlBuildingTest{
+	{
+		route: new(Route).Host("foo.domain.com"),
+		vars:  []string{},
+		url:   "http://foo.domain.com",
+	},
+	{
+		route: new(Route).Host("{subdomain}.domain.com"),
+		vars:  []string{"subdomain", "bar"},
+		url:   "http://bar.domain.com",
+	},
+	{
+		route: new(Route).Host("foo.domain.com").Path("/articles"),
+		vars:  []string{},
+		url:   "http://foo.domain.com/articles",
+	},
+	{
+		route: new(Route).Path("/articles"),
+		vars:  []string{},
+		url:   "/articles",
+	},
+	{
+		route: new(Route).Path("/articles/{category}/{id:[0-9]+}"),
+		vars:  []string{"category", "technology", "id", "42"},
+		url:   "/articles/technology/42",
+	},
+	{
+		route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"),
+		vars:  []string{"subdomain", "foo", "category", "technology", "id", "42"},
+		url:   "http://foo.domain.com/articles/technology/42",
+	},
+}
+
+func TestHeaderMatcher(t *testing.T) {
+	for _, v := range headerMatcherTests {
+		request, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+		for key, value := range v.headers {
+			request.Header.Add(key, value)
+		}
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, request.Header)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, request.Header)
+			}
+		}
+	}
+}
+
+func TestHostMatcher(t *testing.T) {
+	for _, v := range hostMatcherTests {
+		request, _ := http.NewRequest("GET", v.url, nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		vars := routeMatch.Vars
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+			}
+		}
+		if result {
+			if len(vars) != len(v.vars) {
+				t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
+			}
+			for name, value := range vars {
+				if v.vars[name] != value {
+					t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
+				}
+			}
+		} else {
+			if len(vars) != 0 {
+				t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
+			}
+		}
+	}
+}
+
+func TestMethodMatcher(t *testing.T) {
+	for _, v := range methodMatcherTests {
+		request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.method)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.method)
+			}
+		}
+	}
+}
+
+func TestPathMatcher(t *testing.T) {
+	for _, v := range pathMatcherTests {
+		request, _ := http.NewRequest("GET", v.url, nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		vars := routeMatch.Vars
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+			}
+		}
+		if result {
+			if len(vars) != len(v.vars) {
+				t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
+			}
+			for name, value := range vars {
+				if v.vars[name] != value {
+					t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
+				}
+			}
+		} else {
+			if len(vars) != 0 {
+				t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
+			}
+		}
+	}
+}
+
+func TestQueryMatcher(t *testing.T) {
+	for _, v := range queryMatcherTests {
+		request, _ := http.NewRequest("GET", v.url, nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+			}
+		}
+	}
+}
+
+func TestSchemeMatcher(t *testing.T) {
+	for _, v := range queryMatcherTests {
+		request, _ := http.NewRequest("GET", v.url, nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+			}
+		}
+	}
+}
+
+func TestUrlBuilding(t *testing.T) {
+
+	for _, v := range urlBuildingTests {
+		u, _ := v.route.URL(v.vars...)
+		url := u.String()
+		if url != v.url {
+			t.Errorf("expected %v, got %v", v.url, url)
+			/*
+				reversePath := ""
+				reverseHost := ""
+				if v.route.pathTemplate != nil {
+						reversePath = v.route.pathTemplate.Reverse
+				}
+				if v.route.hostTemplate != nil {
+						reverseHost = v.route.hostTemplate.Reverse
+				}
+
+				t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost)
+			*/
+		}
+	}
+
+	ArticleHandler := func(w http.ResponseWriter, r *http.Request) {
+	}
+
+	router := NewRouter()
+	router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article")
+
+	url, _ := router.Get("article").URL("category", "technology", "id", "42")
+	expected := "/articles/technology/42"
+	if url.String() != expected {
+		t.Errorf("Expected %v, got %v", expected, url.String())
+	}
+}
+
+func TestMatchedRouteName(t *testing.T) {
+	routeName := "stock"
+	router := NewRouter()
+	route := router.NewRoute().Path("/products/").Name(routeName)
+
+	url := "http://www.domain.com/products/"
+	request, _ := http.NewRequest("GET", url, nil)
+	var rv RouteMatch
+	ok := router.Match(request, &rv)
+
+	if !ok || rv.Route != route {
+		t.Errorf("Expected same route, got %+v.", rv.Route)
+	}
+
+	retName := rv.Route.GetName()
+	if retName != routeName {
+		t.Errorf("Expected %q, got %q.", routeName, retName)
+	}
+}
+
+func TestSubRouting(t *testing.T) {
+	// Example from docs.
+	router := NewRouter()
+	subrouter := router.NewRoute().Host("www.domain.com").Subrouter()
+	route := subrouter.NewRoute().Path("/products/").Name("products")
+
+	url := "http://www.domain.com/products/"
+	request, _ := http.NewRequest("GET", url, nil)
+	var rv RouteMatch
+	ok := router.Match(request, &rv)
+
+	if !ok || rv.Route != route {
+		t.Errorf("Expected same route, got %+v.", rv.Route)
+	}
+
+	u, _ := router.Get("products").URL()
+	builtUrl := u.String()
+	// Yay, subroute aware of the domain when building!
+	if builtUrl != url {
+		t.Errorf("Expected %q, got %q.", url, builtUrl)
+	}
+}
+
+func TestVariableNames(t *testing.T) {
+	route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}")
+	if route.err == nil {
+		t.Errorf("Expected error for duplicated variable names")
+	}
+}
+
+func TestRedirectSlash(t *testing.T) {
+	var route *Route
+	var routeMatch RouteMatch
+	r := NewRouter()
+
+	r.StrictSlash(false)
+	route = r.NewRoute()
+	if route.strictSlash != false {
+		t.Errorf("Expected false redirectSlash.")
+	}
+
+	r.StrictSlash(true)
+	route = r.NewRoute()
+	if route.strictSlash != true {
+		t.Errorf("Expected true redirectSlash.")
+	}
+
+	route = new(Route)
+	route.strictSlash = true
+	route.Path("/{arg1}/{arg2:[0-9]+}/")
+	request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil)
+	routeMatch = RouteMatch{}
+	_ = route.Match(request, &routeMatch)
+	vars := routeMatch.Vars
+	if vars["arg1"] != "foo" {
+		t.Errorf("Expected foo.")
+	}
+	if vars["arg2"] != "123" {
+		t.Errorf("Expected 123.")
+	}
+	rsp := NewRecorder()
+	routeMatch.Handler.ServeHTTP(rsp, request)
+	if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" {
+		t.Errorf("Expected redirect header.")
+	}
+
+	route = new(Route)
+	route.strictSlash = true
+	route.Path("/{arg1}/{arg2:[0-9]+}")
+	request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil)
+	routeMatch = RouteMatch{}
+	_ = route.Match(request, &routeMatch)
+	vars = routeMatch.Vars
+	if vars["arg1"] != "foo" {
+		t.Errorf("Expected foo.")
+	}
+	if vars["arg2"] != "123" {
+		t.Errorf("Expected 123.")
+	}
+	rsp = NewRecorder()
+	routeMatch.Handler.ServeHTTP(rsp, request)
+	if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" {
+		t.Errorf("Expected redirect header.")
+	}
+}
+
+// Test for the new regexp library, still not available in stable Go.
+func TestNewRegexp(t *testing.T) {
+	var p *routeRegexp
+	var matches []string
+
+	tests := map[string]map[string][]string{
+		"/{foo:a{2}}": {
+			"/a":    nil,
+			"/aa":   {"aa"},
+			"/aaa":  nil,
+			"/aaaa": nil,
+		},
+		"/{foo:a{2,}}": {
+			"/a":    nil,
+			"/aa":   {"aa"},
+			"/aaa":  {"aaa"},
+			"/aaaa": {"aaaa"},
+		},
+		"/{foo:a{2,3}}": {
+			"/a":    nil,
+			"/aa":   {"aa"},
+			"/aaa":  {"aaa"},
+			"/aaaa": nil,
+		},
+		"/{foo:[a-z]{3}}/{bar:[a-z]{2}}": {
+			"/a":       nil,
+			"/ab":      nil,
+			"/abc":     nil,
+			"/abcd":    nil,
+			"/abc/ab":  {"abc", "ab"},
+			"/abc/abc": nil,
+			"/abcd/ab": nil,
+		},
+		`/{foo:\w{3,}}/{bar:\d{2,}}`: {
+			"/a":        nil,
+			"/ab":       nil,
+			"/abc":      nil,
+			"/abc/1":    nil,
+			"/abc/12":   {"abc", "12"},
+			"/abcd/12":  {"abcd", "12"},
+			"/abcd/123": {"abcd", "123"},
+		},
+	}
+
+	for pattern, paths := range tests {
+		p, _ = newRouteRegexp(pattern, false, false, false)
+		for path, result := range paths {
+			matches = p.regexp.FindStringSubmatch(path)
+			if result == nil {
+				if matches != nil {
+					t.Errorf("%v should not match %v.", pattern, path)
+				}
+			} else {
+				if len(matches) != len(result)+1 {
+					t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches))
+				} else {
+					for k, v := range result {
+						if matches[k+1] != v {
+							t.Errorf("Expected %v, got %v.", v, matches[k+1])
+						}
+					}
+				}
+			}
+		}
+	}
+}

+ 247 - 0
third_party/github.com/gorilla/mux/regexp.go

@@ -0,0 +1,247 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"bytes"
+	"fmt"
+	"net/http"
+	"net/url"
+	"regexp"
+	"strings"
+)
+
+// newRouteRegexp parses a route template and returns a routeRegexp,
+// used to match a host or path.
+//
+// It will extract named variables, assemble a regexp to be matched, create
+// a "reverse" template to build URLs and compile regexps to validate variable
+// values used in URL building.
+//
+// Previously we accepted only Python-like identifiers for variable
+// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
+// name and pattern can't be empty, and names can't contain a colon.
+func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*routeRegexp, error) {
+	// Check if it is well-formed.
+	idxs, errBraces := braceIndices(tpl)
+	if errBraces != nil {
+		return nil, errBraces
+	}
+	// Backup the original.
+	template := tpl
+	// Now let's parse it.
+	defaultPattern := "[^/]+"
+	if matchHost {
+		defaultPattern = "[^.]+"
+		matchPrefix, strictSlash = false, false
+	}
+	if matchPrefix {
+		strictSlash = false
+	}
+	// Set a flag for strictSlash.
+	endSlash := false
+	if strictSlash && strings.HasSuffix(tpl, "/") {
+		tpl = tpl[:len(tpl)-1]
+		endSlash = true
+	}
+	varsN := make([]string, len(idxs)/2)
+	varsR := make([]*regexp.Regexp, len(idxs)/2)
+	pattern := bytes.NewBufferString("^")
+	reverse := bytes.NewBufferString("")
+	var end int
+	var err error
+	for i := 0; i < len(idxs); i += 2 {
+		// Set all values we are interested in.
+		raw := tpl[end:idxs[i]]
+		end = idxs[i+1]
+		parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
+		name := parts[0]
+		patt := defaultPattern
+		if len(parts) == 2 {
+			patt = parts[1]
+		}
+		// Name or pattern can't be empty.
+		if name == "" || patt == "" {
+			return nil, fmt.Errorf("mux: missing name or pattern in %q",
+				tpl[idxs[i]:end])
+		}
+		// Build the regexp pattern.
+		fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt)
+		// Build the reverse template.
+		fmt.Fprintf(reverse, "%s%%s", raw)
+		// Append variable name and compiled pattern.
+		varsN[i/2] = name
+		varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
+		if err != nil {
+			return nil, err
+		}
+	}
+	// Add the remaining.
+	raw := tpl[end:]
+	pattern.WriteString(regexp.QuoteMeta(raw))
+	if strictSlash {
+		pattern.WriteString("[/]?")
+	}
+	if !matchPrefix {
+		pattern.WriteByte('$')
+	}
+	reverse.WriteString(raw)
+	if endSlash {
+		reverse.WriteByte('/')
+	}
+	// Compile full regexp.
+	reg, errCompile := regexp.Compile(pattern.String())
+	if errCompile != nil {
+		return nil, errCompile
+	}
+	// Done!
+	return &routeRegexp{
+		template:  template,
+		matchHost: matchHost,
+		regexp:    reg,
+		reverse:   reverse.String(),
+		varsN:     varsN,
+		varsR:     varsR,
+	}, nil
+}
+
+// routeRegexp stores a regexp to match a host or path and information to
+// collect and validate route variables.
+type routeRegexp struct {
+	// The unmodified template.
+	template string
+	// True for host match, false for path match.
+	matchHost bool
+	// Expanded regexp.
+	regexp *regexp.Regexp
+	// Reverse template.
+	reverse string
+	// Variable names.
+	varsN []string
+	// Variable regexps (validators).
+	varsR []*regexp.Regexp
+}
+
+// Match matches the regexp against the URL host or path.
+func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
+	if !r.matchHost {
+		return r.regexp.MatchString(req.URL.Path)
+	}
+	return r.regexp.MatchString(getHost(req))
+}
+
+// url builds a URL part using the given values.
+func (r *routeRegexp) url(pairs ...string) (string, error) {
+	values, err := mapFromPairs(pairs...)
+	if err != nil {
+		return "", err
+	}
+	urlValues := make([]interface{}, len(r.varsN))
+	for k, v := range r.varsN {
+		value, ok := values[v]
+		if !ok {
+			return "", fmt.Errorf("mux: missing route variable %q", v)
+		}
+		urlValues[k] = value
+	}
+	rv := fmt.Sprintf(r.reverse, urlValues...)
+	if !r.regexp.MatchString(rv) {
+		// The URL is checked against the full regexp, instead of checking
+		// individual variables. This is faster but to provide a good error
+		// message, we check individual regexps if the URL doesn't match.
+		for k, v := range r.varsN {
+			if !r.varsR[k].MatchString(values[v]) {
+				return "", fmt.Errorf(
+					"mux: variable %q doesn't match, expected %q", values[v],
+					r.varsR[k].String())
+			}
+		}
+	}
+	return rv, nil
+}
+
+// braceIndices returns the first level curly brace indices from a string.
+// It returns an error in case of unbalanced braces.
+func braceIndices(s string) ([]int, error) {
+	var level, idx int
+	idxs := make([]int, 0)
+	for i := 0; i < len(s); i++ {
+		switch s[i] {
+		case '{':
+			if level++; level == 1 {
+				idx = i
+			}
+		case '}':
+			if level--; level == 0 {
+				idxs = append(idxs, idx, i+1)
+			} else if level < 0 {
+				return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
+			}
+		}
+	}
+	if level != 0 {
+		return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
+	}
+	return idxs, nil
+}
+
+// ----------------------------------------------------------------------------
+// routeRegexpGroup
+// ----------------------------------------------------------------------------
+
+// routeRegexpGroup groups the route matchers that carry variables.
+type routeRegexpGroup struct {
+	host *routeRegexp
+	path *routeRegexp
+}
+
+// setMatch extracts the variables from the URL once a route matches.
+func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
+	// Store host variables.
+	if v.host != nil {
+		hostVars := v.host.regexp.FindStringSubmatch(getHost(req))
+		if hostVars != nil {
+			for k, v := range v.host.varsN {
+				m.Vars[v] = hostVars[k+1]
+			}
+		}
+	}
+	// Store path variables.
+	if v.path != nil {
+		pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path)
+		if pathVars != nil {
+			for k, v := range v.path.varsN {
+				m.Vars[v] = pathVars[k+1]
+			}
+			// Check if we should redirect.
+			if r.strictSlash {
+				p1 := strings.HasSuffix(req.URL.Path, "/")
+				p2 := strings.HasSuffix(v.path.template, "/")
+				if p1 != p2 {
+					u, _ := url.Parse(req.URL.String())
+					if p1 {
+						u.Path = u.Path[:len(u.Path)-1]
+					} else {
+						u.Path += "/"
+					}
+					m.Handler = http.RedirectHandler(u.String(), 301)
+				}
+			}
+		}
+	}
+}
+
+// getHost tries its best to return the request host.
+func getHost(r *http.Request) string {
+	if !r.URL.IsAbs() {
+		host := r.Host
+		// Slice off any port information.
+		if i := strings.Index(host, ":"); i != -1 {
+			host = host[:i]
+		}
+		return host
+	}
+	return r.URL.Host
+}

+ 499 - 0
third_party/github.com/gorilla/mux/route.go

@@ -0,0 +1,499 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+// Route stores information to match a request and build URLs.
+type Route struct {
+	// Parent where the route was registered (a Router).
+	parent parentRoute
+	// Request handler for the route.
+	handler http.Handler
+	// List of matchers.
+	matchers []matcher
+	// Manager for the variables from host and path.
+	regexp *routeRegexpGroup
+	// If true, when the path pattern is "/path/", accessing "/path" will
+	// redirect to the former and vice versa.
+	strictSlash bool
+	// If true, this route never matches: it is only used to build URLs.
+	buildOnly bool
+	// The name used to build URLs.
+	name string
+	// Error resulted from building a route.
+	err error
+}
+
+// Match matches the route against the request.
+func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
+	if r.buildOnly || r.err != nil {
+		return false
+	}
+	// Match everything.
+	for _, m := range r.matchers {
+		if matched := m.Match(req, match); !matched {
+			return false
+		}
+	}
+	// Yay, we have a match. Let's collect some info about it.
+	if match.Route == nil {
+		match.Route = r
+	}
+	if match.Handler == nil {
+		match.Handler = r.handler
+	}
+	if match.Vars == nil {
+		match.Vars = make(map[string]string)
+	}
+	// Set variables.
+	if r.regexp != nil {
+		r.regexp.setMatch(req, match, r)
+	}
+	return true
+}
+
+// ----------------------------------------------------------------------------
+// Route attributes
+// ----------------------------------------------------------------------------
+
+// GetError returns an error resulted from building the route, if any.
+func (r *Route) GetError() error {
+	return r.err
+}
+
+// BuildOnly sets the route to never match: it is only used to build URLs.
+func (r *Route) BuildOnly() *Route {
+	r.buildOnly = true
+	return r
+}
+
+// Handler --------------------------------------------------------------------
+
+// Handler sets a handler for the route.
+func (r *Route) Handler(handler http.Handler) *Route {
+	if r.err == nil {
+		r.handler = handler
+	}
+	return r
+}
+
+// HandlerFunc sets a handler function for the route.
+func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
+	return r.Handler(http.HandlerFunc(f))
+}
+
+// GetHandler returns the handler for the route, if any.
+func (r *Route) GetHandler() http.Handler {
+	return r.handler
+}
+
+// Name -----------------------------------------------------------------------
+
+// Name sets the name for the route, used to build URLs.
+// If the name was registered already it will be overwritten.
+func (r *Route) Name(name string) *Route {
+	if r.name != "" {
+		r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
+			r.name, name)
+	}
+	if r.err == nil {
+		r.name = name
+		r.getNamedRoutes()[name] = r
+	}
+	return r
+}
+
+// GetName returns the name for the route, if any.
+func (r *Route) GetName() string {
+	return r.name
+}
+
+// ----------------------------------------------------------------------------
+// Matchers
+// ----------------------------------------------------------------------------
+
+// matcher types try to match a request.
+type matcher interface {
+	Match(*http.Request, *RouteMatch) bool
+}
+
+// addMatcher adds a matcher to the route.
+func (r *Route) addMatcher(m matcher) *Route {
+	if r.err == nil {
+		r.matchers = append(r.matchers, m)
+	}
+	return r
+}
+
+// addRegexpMatcher adds a host or path matcher and builder to a route.
+func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error {
+	if r.err != nil {
+		return r.err
+	}
+	r.regexp = r.getRegexpGroup()
+	if !matchHost {
+		if len(tpl) == 0 || tpl[0] != '/' {
+			return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
+		}
+		if r.regexp.path != nil {
+			tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
+		}
+	}
+	rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, r.strictSlash)
+	if err != nil {
+		return err
+	}
+	if matchHost {
+		if r.regexp.path != nil {
+			if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
+				return err
+			}
+		}
+		r.regexp.host = rr
+	} else {
+		if r.regexp.host != nil {
+			if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
+				return err
+			}
+		}
+		r.regexp.path = rr
+	}
+	r.addMatcher(rr)
+	return nil
+}
+
+// Headers --------------------------------------------------------------------
+
+// headerMatcher matches the request against header values.
+type headerMatcher map[string]string
+
+func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchMap(m, r.Header, true)
+}
+
+// Headers adds a matcher for request header values.
+// It accepts a sequence of key/value pairs to be matched. For example:
+//
+//     r := mux.NewRouter()
+//     r.Headers("Content-Type", "application/json",
+//               "X-Requested-With", "XMLHttpRequest")
+//
+// The above route will only match if both request header values match.
+//
+// It the value is an empty string, it will match any value if the key is set.
+func (r *Route) Headers(pairs ...string) *Route {
+	if r.err == nil {
+		var headers map[string]string
+		headers, r.err = mapFromPairs(pairs...)
+		return r.addMatcher(headerMatcher(headers))
+	}
+	return r
+}
+
+// Host -----------------------------------------------------------------------
+
+// Host adds a matcher for the URL host.
+// It accepts a template with zero or more URL variables enclosed by {}.
+// Variables can define an optional regexp pattern to me matched:
+//
+// - {name} matches anything until the next dot.
+//
+// - {name:pattern} matches the given regexp pattern.
+//
+// For example:
+//
+//     r := mux.NewRouter()
+//     r.Host("www.domain.com")
+//     r.Host("{subdomain}.domain.com")
+//     r.Host("{subdomain:[a-z]+}.domain.com")
+//
+// Variable names must be unique in a given route. They can be retrieved
+// calling mux.Vars(request).
+func (r *Route) Host(tpl string) *Route {
+	r.err = r.addRegexpMatcher(tpl, true, false)
+	return r
+}
+
+// MatcherFunc ----------------------------------------------------------------
+
+// MatcherFunc is the function signature used by custom matchers.
+type MatcherFunc func(*http.Request, *RouteMatch) bool
+
+func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
+	return m(r, match)
+}
+
+// MatcherFunc adds a custom function to be used as request matcher.
+func (r *Route) MatcherFunc(f MatcherFunc) *Route {
+	return r.addMatcher(f)
+}
+
+// Methods --------------------------------------------------------------------
+
+// methodMatcher matches the request against HTTP methods.
+type methodMatcher []string
+
+func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchInArray(m, r.Method)
+}
+
+// Methods adds a matcher for HTTP methods.
+// It accepts a sequence of one or more methods to be matched, e.g.:
+// "GET", "POST", "PUT".
+func (r *Route) Methods(methods ...string) *Route {
+	for k, v := range methods {
+		methods[k] = strings.ToUpper(v)
+	}
+	return r.addMatcher(methodMatcher(methods))
+}
+
+// Path -----------------------------------------------------------------------
+
+// Path adds a matcher for the URL path.
+// It accepts a template with zero or more URL variables enclosed by {}.
+// Variables can define an optional regexp pattern to me matched:
+//
+// - {name} matches anything until the next slash.
+//
+// - {name:pattern} matches the given regexp pattern.
+//
+// For example:
+//
+//     r := mux.NewRouter()
+//     r.Path("/products/").Handler(ProductsHandler)
+//     r.Path("/products/{key}").Handler(ProductsHandler)
+//     r.Path("/articles/{category}/{id:[0-9]+}").
+//       Handler(ArticleHandler)
+//
+// Variable names must be unique in a given route. They can be retrieved
+// calling mux.Vars(request).
+func (r *Route) Path(tpl string) *Route {
+	r.err = r.addRegexpMatcher(tpl, false, false)
+	return r
+}
+
+// PathPrefix -----------------------------------------------------------------
+
+// PathPrefix adds a matcher for the URL path prefix.
+func (r *Route) PathPrefix(tpl string) *Route {
+	r.strictSlash = false
+	r.err = r.addRegexpMatcher(tpl, false, true)
+	return r
+}
+
+// Query ----------------------------------------------------------------------
+
+// queryMatcher matches the request against URL queries.
+type queryMatcher map[string]string
+
+func (m queryMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchMap(m, r.URL.Query(), false)
+}
+
+// Queries adds a matcher for URL query values.
+// It accepts a sequence of key/value pairs. For example:
+//
+//     r := mux.NewRouter()
+//     r.Queries("foo", "bar", "baz", "ding")
+//
+// The above route will only match if the URL contains the defined queries
+// values, e.g.: ?foo=bar&baz=ding.
+//
+// It the value is an empty string, it will match any value if the key is set.
+func (r *Route) Queries(pairs ...string) *Route {
+	if r.err == nil {
+		var queries map[string]string
+		queries, r.err = mapFromPairs(pairs...)
+		return r.addMatcher(queryMatcher(queries))
+	}
+	return r
+}
+
+// Schemes --------------------------------------------------------------------
+
+// schemeMatcher matches the request against URL schemes.
+type schemeMatcher []string
+
+func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchInArray(m, r.URL.Scheme)
+}
+
+// Schemes adds a matcher for URL schemes.
+// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
+func (r *Route) Schemes(schemes ...string) *Route {
+	for k, v := range schemes {
+		schemes[k] = strings.ToLower(v)
+	}
+	return r.addMatcher(schemeMatcher(schemes))
+}
+
+// Subrouter ------------------------------------------------------------------
+
+// Subrouter creates a subrouter for the route.
+//
+// It will test the inner routes only if the parent route matched. For example:
+//
+//     r := mux.NewRouter()
+//     s := r.Host("www.domain.com").Subrouter()
+//     s.HandleFunc("/products/", ProductsHandler)
+//     s.HandleFunc("/products/{key}", ProductHandler)
+//     s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
+//
+// Here, the routes registered in the subrouter won't be tested if the host
+// doesn't match.
+func (r *Route) Subrouter() *Router {
+	router := &Router{parent: r, strictSlash: r.strictSlash}
+	r.addMatcher(router)
+	return router
+}
+
+// ----------------------------------------------------------------------------
+// URL building
+// ----------------------------------------------------------------------------
+
+// URL builds a URL for the route.
+//
+// It accepts a sequence of key/value pairs for the route variables. For
+// example, given this route:
+//
+//     r := mux.NewRouter()
+//     r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+//       Name("article")
+//
+// ...a URL for it can be built using:
+//
+//     url, err := r.Get("article").URL("category", "technology", "id", "42")
+//
+// ...which will return an url.URL with the following path:
+//
+//     "/articles/technology/42"
+//
+// This also works for host variables:
+//
+//     r := mux.NewRouter()
+//     r.Host("{subdomain}.domain.com").
+//       HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+//       Name("article")
+//
+//     // url.String() will be "http://news.domain.com/articles/technology/42"
+//     url, err := r.Get("article").URL("subdomain", "news",
+//                                      "category", "technology",
+//                                      "id", "42")
+//
+// All variables defined in the route are required, and their values must
+// conform to the corresponding patterns.
+func (r *Route) URL(pairs ...string) (*url.URL, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if r.regexp == nil {
+		return nil, errors.New("mux: route doesn't have a host or path")
+	}
+	var scheme, host, path string
+	var err error
+	if r.regexp.host != nil {
+		// Set a default scheme.
+		scheme = "http"
+		if host, err = r.regexp.host.url(pairs...); err != nil {
+			return nil, err
+		}
+	}
+	if r.regexp.path != nil {
+		if path, err = r.regexp.path.url(pairs...); err != nil {
+			return nil, err
+		}
+	}
+	return &url.URL{
+		Scheme: scheme,
+		Host:   host,
+		Path:   path,
+	}, nil
+}
+
+// URLHost builds the host part of the URL for a route. See Route.URL().
+//
+// The route must have a host defined.
+func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if r.regexp == nil || r.regexp.host == nil {
+		return nil, errors.New("mux: route doesn't have a host")
+	}
+	host, err := r.regexp.host.url(pairs...)
+	if err != nil {
+		return nil, err
+	}
+	return &url.URL{
+		Scheme: "http",
+		Host:   host,
+	}, nil
+}
+
+// URLPath builds the path part of the URL for a route. See Route.URL().
+//
+// The route must have a path defined.
+func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if r.regexp == nil || r.regexp.path == nil {
+		return nil, errors.New("mux: route doesn't have a path")
+	}
+	path, err := r.regexp.path.url(pairs...)
+	if err != nil {
+		return nil, err
+	}
+	return &url.URL{
+		Path: path,
+	}, nil
+}
+
+// ----------------------------------------------------------------------------
+// parentRoute
+// ----------------------------------------------------------------------------
+
+// parentRoute allows routes to know about parent host and path definitions.
+type parentRoute interface {
+	getNamedRoutes() map[string]*Route
+	getRegexpGroup() *routeRegexpGroup
+}
+
+// getNamedRoutes returns the map where named routes are registered.
+func (r *Route) getNamedRoutes() map[string]*Route {
+	if r.parent == nil {
+		// During tests router is not always set.
+		r.parent = NewRouter()
+	}
+	return r.parent.getNamedRoutes()
+}
+
+// getRegexpGroup returns regexp definitions from this route.
+func (r *Route) getRegexpGroup() *routeRegexpGroup {
+	if r.regexp == nil {
+		if r.parent == nil {
+			// During tests router is not always set.
+			r.parent = NewRouter()
+		}
+		regexp := r.parent.getRegexpGroup()
+		if regexp == nil {
+			r.regexp = new(routeRegexpGroup)
+		} else {
+			// Copy.
+			r.regexp = &routeRegexpGroup{
+				host: regexp.host,
+				path: regexp.path,
+			}
+		}
+	}
+	return r.regexp
+}

+ 8 - 179
util.go

@@ -1,121 +1,19 @@
 package main
 package main
 
 
 import (
 import (
-	"encoding/json"
-	"fmt"
-	"io"
 	"net"
 	"net"
-	"net/http"
 	"net/url"
 	"net/url"
 	"os"
 	"os"
 	"os/signal"
 	"os/signal"
 	"runtime/pprof"
 	"runtime/pprof"
-	"strconv"
-	"time"
 
 
-	etcdErr "github.com/coreos/etcd/error"
-	"github.com/coreos/etcd/store"
-	"github.com/coreos/go-log/log"
-	"github.com/coreos/go-raft"
+	"github.com/coreos/etcd/log"
 )
 )
 
 
-//--------------------------------------
-// etcd http Helper
-//--------------------------------------
-
-// Convert string duration to time format
-func durationToExpireTime(strDuration string) (time.Time, error) {
-	if strDuration != "" {
-		duration, err := strconv.Atoi(strDuration)
-
-		if err != nil {
-			return store.Permanent, err
-		}
-		return time.Now().Add(time.Second * (time.Duration)(duration)), nil
-
-	} else {
-		return store.Permanent, nil
-	}
-}
-
 //--------------------------------------
 //--------------------------------------
 // HTTP Utilities
 // HTTP Utilities
 //--------------------------------------
 //--------------------------------------
 
 
-func (r *raftServer) dispatch(c Command, w http.ResponseWriter, req *http.Request, toURL func(name string) (string, bool)) error {
-	if r.State() == raft.Leader {
-		if response, err := r.Do(c); err != nil {
-			return err
-		} else {
-			if response == nil {
-				return etcdErr.NewError(300, "Empty response from raft", store.UndefIndex, store.UndefTerm)
-			}
-
-			event, ok := response.(*store.Event)
-			if ok {
-				bytes, err := json.Marshal(event)
-				if err != nil {
-					fmt.Println(err)
-				}
-
-				w.Header().Add("X-Etcd-Index", fmt.Sprint(event.Index))
-				w.Header().Add("X-Etcd-Term", fmt.Sprint(event.Term))
-				w.WriteHeader(http.StatusOK)
-				w.Write(bytes)
-
-				return nil
-			}
-
-			bytes, _ := response.([]byte)
-			w.WriteHeader(http.StatusOK)
-			w.Write(bytes)
-
-			return nil
-		}
-
-	} else {
-		leader := r.Leader()
-		// current no leader
-		if leader == "" {
-			return etcdErr.NewError(300, "", store.UndefIndex, store.UndefTerm)
-		}
-		url, _ := toURL(leader)
-
-		redirect(url, w, req)
-
-		return nil
-	}
-}
-
-func redirect(hostname string, w http.ResponseWriter, req *http.Request) {
-	path := req.URL.Path
-
-	url := hostname + path
-
-	debugf("Redirect to %s", url)
-
-	http.Redirect(w, req, url, http.StatusTemporaryRedirect)
-}
-
-func decodeJsonRequest(req *http.Request, data interface{}) error {
-	decoder := json.NewDecoder(req.Body)
-	if err := decoder.Decode(&data); err != nil && err != io.EOF {
-		warnf("Malformed json request: %v", err)
-		return fmt.Errorf("Malformed json request: %v", err)
-	}
-	return nil
-}
-
-func encodeJsonResponse(w http.ResponseWriter, status int, data interface{}) {
-	w.Header().Set("Content-Type", "application/json")
-	w.WriteHeader(status)
-
-	if data != nil {
-		encoder := json.NewEncoder(w)
-		encoder.Encode(data)
-	}
-}
-
 // sanitizeURL will cleanup a host string in the format hostname:port and
 // sanitizeURL will cleanup a host string in the format hostname:port and
 // attach a schema.
 // attach a schema.
 func sanitizeURL(host string, defaultScheme string) string {
 func sanitizeURL(host string, defaultScheme string) string {
@@ -126,13 +24,13 @@ func sanitizeURL(host string, defaultScheme string) string {
 
 
 	p, err := url.Parse(host)
 	p, err := url.Parse(host)
 	if err != nil {
 	if err != nil {
-		fatal(err)
+		log.Fatal(err)
 	}
 	}
 
 
 	// Make sure the host is in Host:Port format
 	// Make sure the host is in Host:Port format
 	_, _, err = net.SplitHostPort(host)
 	_, _, err = net.SplitHostPort(host)
 	if err != nil {
 	if err != nil {
-		fatal(err)
+		log.Fatal(err)
 	}
 	}
 
 
 	p = &url.URL{Host: host, Scheme: defaultScheme}
 	p = &url.URL{Host: host, Scheme: defaultScheme}
@@ -145,12 +43,12 @@ func sanitizeURL(host string, defaultScheme string) string {
 func sanitizeListenHost(listen string, advertised string) string {
 func sanitizeListenHost(listen string, advertised string) string {
 	aurl, err := url.Parse(advertised)
 	aurl, err := url.Parse(advertised)
 	if err != nil {
 	if err != nil {
-		fatal(err)
+		log.Fatal(err)
 	}
 	}
 
 
 	ahost, aport, err := net.SplitHostPort(aurl.Host)
 	ahost, aport, err := net.SplitHostPort(aurl.Host)
 	if err != nil {
 	if err != nil {
-		fatal(err)
+		log.Fatal(err)
 	}
 	}
 
 
 	// If the listen host isn't set use the advertised host
 	// If the listen host isn't set use the advertised host
@@ -163,54 +61,10 @@ func sanitizeListenHost(listen string, advertised string) string {
 
 
 func check(err error) {
 func check(err error) {
 	if err != nil {
 	if err != nil {
-		fatal(err)
+		log.Fatal(err)
 	}
 	}
 }
 }
 
 
-func getNodePath(urlPath string) string {
-	pathPrefixLen := len("/" + version + "/keys")
-	return urlPath[pathPrefixLen:]
-}
-
-//--------------------------------------
-// Log
-//--------------------------------------
-
-var logger *log.Logger = log.New("etcd", false,
-	log.CombinedSink(os.Stdout, "[%s] %s %-9s | %s\n", []string{"prefix", "time", "priority", "message"}))
-
-func infof(format string, v ...interface{}) {
-	logger.Infof(format, v...)
-}
-
-func debugf(format string, v ...interface{}) {
-	if verbose {
-		logger.Debugf(format, v...)
-	}
-}
-
-func debug(v ...interface{}) {
-	if verbose {
-		logger.Debug(v...)
-	}
-}
-
-func warnf(format string, v ...interface{}) {
-	logger.Warningf(format, v...)
-}
-
-func warn(v ...interface{}) {
-	logger.Warning(v...)
-}
-
-func fatalf(format string, v ...interface{}) {
-	logger.Fatalf(format, v...)
-}
-
-func fatal(v ...interface{}) {
-	logger.Fatalln(v...)
-}
-
 //--------------------------------------
 //--------------------------------------
 // CPU profile
 // CPU profile
 //--------------------------------------
 //--------------------------------------
@@ -218,7 +72,7 @@ func runCPUProfile() {
 
 
 	f, err := os.Create(cpuprofile)
 	f, err := os.Create(cpuprofile)
 	if err != nil {
 	if err != nil {
-		fatal(err)
+		log.Fatal(err)
 	}
 	}
 	pprof.StartCPUProfile(f)
 	pprof.StartCPUProfile(f)
 
 
@@ -226,34 +80,9 @@ func runCPUProfile() {
 	signal.Notify(c, os.Interrupt)
 	signal.Notify(c, os.Interrupt)
 	go func() {
 	go func() {
 		for sig := range c {
 		for sig := range c {
-			infof("captured %v, stopping profiler and exiting..", sig)
+			log.Infof("captured %v, stopping profiler and exiting..", sig)
 			pprof.StopCPUProfile()
 			pprof.StopCPUProfile()
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
 	}()
 	}()
 }
 }
-
-//--------------------------------------
-// Testing
-//--------------------------------------
-func directSet() {
-	c := make(chan bool, 1000)
-	for i := 0; i < 1000; i++ {
-		go send(c)
-	}
-
-	for i := 0; i < 1000; i++ {
-		<-c
-	}
-}
-
-func send(c chan bool) {
-	for i := 0; i < 10; i++ {
-		command := &UpdateCommand{}
-		command.Key = "foo"
-		command.Value = "bar"
-		command.ExpireTime = time.Unix(0, 0)
-		//r.Do(command)
-	}
-	c <- true
-}