[
  {
    "path": ".gitignore",
    "content": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2013 raftd contributors\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "raftd\n=====\n\n![unmaintained](http://img.shields.io/badge/status-unmaintained-red.png)\n\n**NOTE**: This project is unmaintained. If you are using goraft in a project\nand want to carry the project forward please file an issue with your ideas and\nintentions. The original project authors have created new raft implementations\nnow used in [etcd](https://godoc.org/github.com/coreos/etcd/raft) and [InfluxDB](https://godoc.org/github.com/influxdb/influxdb/raft).\n\nIf you want to see a simple raft key-value store see the [etcd/raft example](https://github.com/coreos/etcd/tree/master/contrib/raftexample).\n\n## Overview\n\nThe raftd server is a reference implementation for using the [goraft](https://github.com/goraft/raft) library.\nThis library provides a distributed consensus protocol based on the Raft protocol as described by Diego Ongaro and John Ousterhout in their paper, [In Search of an Understandable Consensus Algorithm](https://ramcloud.stanford.edu/wiki/download/attachments/11370504/raft.pdf).\nThis protocol is based on Paxos but is architected to be more understandable.\nIt is similar to other log-based distributed consensus systems such as [Google's Chubby](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CDAQFjAA&url=http%3A%2F%2Fresearch.google.com%2Farchive%2Fchubby.html&ei=i9OGUerTJKbtiwLkiICoCQ&usg=AFQjCNEmFWlaB_iXQfEjMcMwPaYTphO6bA&sig2=u1vefM2ZOZu_ZVIZGynt1A&bvm=bv.45960087,d.cGE) or [Heroku's doozerd](https://github.com/ha/doozerd).\n\nThis reference implementation is very simple.\nIt is a key/value database with the following HTTP API:\n\n```\n# Set the value of a key.\n$ curl -X POST http://localhost:4001/db/my_key -d 'FOO'\n```\n\n```\n# Retrieve the value for a given key.\n$ curl http://localhost:4001/db/my_key\nFOO\n```\n\nAll the values sent to the leader will be propagated to the other servers in the cluster.\nThis reference implementation does not support command forwarding.\nIf you try to send a change to a follower then it will simply be denied.\n\n\n## Running\n\nFirst, install raftd:\n\n```sh\n$ go get github.com/goraft/raftd\n```\n\nTo start the first node in your cluster, simply specify a port and a directory where the data will be stored:\n\n```sh\n$ raftd -p 4001 ~/node.1\n```\n\nTo add nodes to the cluster, you'll need to start on a different port and use a different data directory.\nYou'll also need to specify the host/port of the leader of the cluster to join:\n\n```sh\n$ raftd -p 4002 -join localhost:4001 ~/node.2\n```\n\nWhen you restart the node, it's already been joined to the cluster so you can remove the `-join` argument.\n\nFinally, you can add one more node:\n\n```sh\n$ raftd -p 4003 -join localhost:4001 ~/node.3\n```\n\nNow when you set values to the leader:\n\n```sh\n$ curl -XPOST localhost:4001/db/foo -d 'bar'\n```\n\nThe values will be propagated to the followers:\n\n```sh\n$ curl localhost:4001/db/foo\nbar\n$ curl localhost:4002/db/foo\nbar\n$ curl localhost:4003/db/foo\nbar\n```\n\nKilling the leader will automatically elect a new leader.\nIf you kill and restart the first node and try to set a value you'll receive:\n\n```sh\n$ curl -XPOST localhost:4001/db/foo -d 'bar'\nraft.Server: Not current leader\n```\n\nLeader forwarding is not implemented in the reference implementation.\n\n\n## FAQ\n\n### Why is command forwarding not implemented?\n\nCommand forwarding is a nice feature to have because it allows a client to send a command to any server and have it pushed to the current leader. This lets your client code stay simple. However, now you have an additional point of failure in your remote call. If the intermediate server crashes while delivering the command then your client will still need to know how to retry its command. Since this retry logic needs to be in your client code, adding command forwarding doesn't provide any benefit.\n\n### Why isn't feature X implemented?\n\nRaftd is meant to be a basic reference implementation. As such, it's aim is to provide the smallest, simplest implementation required to get someone off the ground and using go-raft in their project. If you have questions on how to implement a given feature, please add a Github Issue and we can provide instructions in this README.\n\n\n## Debugging\n\nIf you want to see more detail then you can specify several options for logging:\n\n```\n-v       Enables verbose raftd logging.\n-debug   Enables debug-level raft logging.\n-trace   Enables trace-level raft logging.\n```\n\nIf you're having an issue getting `raftd` running, the `-debug` and `-trace` options can be really useful.\n\n\n## Caveats\n\nOne issue with running a 2-node distributed consensus protocol is that we need both servers operational to make a quorum and to perform an actions on the server.\nSo if we kill one of the servers at this point, we will not be able to update the system (since we can't replicate to a majority).\nYou will need to add additional nodes to allow failures to not affect the system.\nFor example, with 3 nodes you can have 1 node fail.\nWith 5 nodes you can have 2 nodes fail.\n\n"
  },
  {
    "path": "command/write_command.go",
    "content": "package command\n\nimport (\n\t\"github.com/goraft/raft\"\n\t\"github.com/goraft/raftd/db\"\n)\n\n// This command writes a value to a key.\ntype WriteCommand struct {\n\tKey   string `json:\"key\"`\n\tValue string `json:\"value\"`\n}\n\n// Creates a new write command.\nfunc NewWriteCommand(key string, value string) *WriteCommand {\n\treturn &WriteCommand{\n\t\tKey:   key,\n\t\tValue: value,\n\t}\n}\n\n// The name of the command in the log.\nfunc (c *WriteCommand) CommandName() string {\n\treturn \"write\"\n}\n\n// Writes a value to a key.\nfunc (c *WriteCommand) Apply(server raft.Server) (interface{}, error) {\n\tdb := server.Context().(*db.DB)\n\tdb.Put(c.Key, c.Value)\n\treturn nil, nil\n}\n"
  },
  {
    "path": "db/db.go",
    "content": "package db\n\nimport (\n\t\"sync\"\n)\n\n// The key-value database.\ntype DB struct {\n\tdata  map[string]string\n\tmutex sync.RWMutex\n}\n\n// Creates a new database.\nfunc New() *DB {\n\treturn &DB{\n\t\tdata: make(map[string]string),\n\t}\n}\n\n// Retrieves the value for a given key.\nfunc (db *DB) Get(key string) string {\n\tdb.mutex.RLock()\n\tdefer db.mutex.RUnlock()\n\treturn db.data[key]\n}\n\n// Sets the value for a given key.\nfunc (db *DB) Put(key string, value string) {\n\tdb.mutex.Lock()\n\tdefer db.mutex.Unlock()\n\tdb.data[key] = value\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"github.com/goraft/raft\"\n\t\"github.com/goraft/raftd/command\"\n\t\"github.com/goraft/raftd/server\"\n\t\"log\"\n\t\"math/rand\"\n\t\"os\"\n\t\"time\"\n)\n\nvar verbose bool\nvar trace bool\nvar debug bool\nvar host string\nvar port int\nvar join string\n\nfunc init() {\n\tflag.BoolVar(&verbose, \"v\", false, \"verbose logging\")\n\tflag.BoolVar(&trace, \"trace\", false, \"Raft trace debugging\")\n\tflag.BoolVar(&debug, \"debug\", false, \"Raft debugging\")\n\tflag.StringVar(&host, \"h\", \"localhost\", \"hostname\")\n\tflag.IntVar(&port, \"p\", 4001, \"port\")\n\tflag.StringVar(&join, \"join\", \"\", \"host:port of leader to join\")\n\tflag.Usage = func() {\n\t\tfmt.Fprintf(os.Stderr, \"Usage: %s [arguments] <data-path> \\n\", os.Args[0])\n\t\tflag.PrintDefaults()\n\t}\n}\n\nfunc main() {\n\tlog.SetFlags(0)\n\tflag.Parse()\n\tif verbose {\n\t\tlog.Print(\"Verbose logging enabled.\")\n\t}\n\tif trace {\n\t\traft.SetLogLevel(raft.Trace)\n\t\tlog.Print(\"Raft trace debugging enabled.\")\n\t} else if debug {\n\t\traft.SetLogLevel(raft.Debug)\n\t\tlog.Print(\"Raft debugging enabled.\")\n\t}\n\n\trand.Seed(time.Now().UnixNano())\n\n\t// Setup commands.\n\traft.RegisterCommand(&command.WriteCommand{})\n\n\t// Set the data directory.\n\tif flag.NArg() == 0 {\n\t\tflag.Usage()\n\t\tlog.Fatal(\"Data path argument required\")\n\t}\n\tpath := flag.Arg(0)\n\tif err := os.MkdirAll(path, 0744); err != nil {\n\t\tlog.Fatalf(\"Unable to create path: %v\", err)\n\t}\n\n\tlog.SetFlags(log.LstdFlags)\n\ts := server.New(path, host, port)\n\tlog.Fatal(s.ListenAndServe(join))\n}\n"
  },
  {
    "path": "server/server.go",
    "content": "package server\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/goraft/raft\"\n\t\"github.com/goraft/raftd/command\"\n\t\"github.com/goraft/raftd/db\"\n\t\"github.com/gorilla/mux\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n)\n\n// The raftd server is a combination of the Raft server and an HTTP\n// server which acts as the transport.\ntype Server struct {\n\tname       string\n\thost       string\n\tport       int\n\tpath       string\n\trouter     *mux.Router\n\traftServer raft.Server\n\thttpServer *http.Server\n\tdb         *db.DB\n\tmutex      sync.RWMutex\n}\n\n// Creates a new server.\nfunc New(path string, host string, port int) *Server {\n\ts := &Server{\n\t\thost:   host,\n\t\tport:   port,\n\t\tpath:   path,\n\t\tdb:     db.New(),\n\t\trouter: mux.NewRouter(),\n\t}\n\n\t// Read existing name or generate a new one.\n\tif b, err := ioutil.ReadFile(filepath.Join(path, \"name\")); err == nil {\n\t\ts.name = string(b)\n\t} else {\n\t\ts.name = fmt.Sprintf(\"%07x\", rand.Int())[0:7]\n\t\tif err = ioutil.WriteFile(filepath.Join(path, \"name\"), []byte(s.name), 0644); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\treturn s\n}\n\n// Returns the connection string.\nfunc (s *Server) connectionString() string {\n\treturn fmt.Sprintf(\"http://%s:%d\", s.host, s.port)\n}\n\n// Starts the server.\nfunc (s *Server) ListenAndServe(leader string) error {\n\tvar err error\n\n\tlog.Printf(\"Initializing Raft Server: %s\", s.path)\n\n\t// Initialize and start Raft server.\n\ttransporter := raft.NewHTTPTransporter(\"/raft\", 200*time.Millisecond)\n\ts.raftServer, err = raft.NewServer(s.name, s.path, transporter, nil, s.db, \"\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\ttransporter.Install(s.raftServer, s)\n\ts.raftServer.Start()\n\n\tif leader != \"\" {\n\t\t// Join to leader if specified.\n\n\t\tlog.Println(\"Attempting to join leader:\", leader)\n\n\t\tif !s.raftServer.IsLogEmpty() {\n\t\t\tlog.Fatal(\"Cannot join with an existing log\")\n\t\t}\n\t\tif err := s.Join(leader); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t} else if s.raftServer.IsLogEmpty() {\n\t\t// Initialize the server by joining itself.\n\n\t\tlog.Println(\"Initializing new cluster\")\n\n\t\t_, err := s.raftServer.Do(&raft.DefaultJoinCommand{\n\t\t\tName:             s.raftServer.Name(),\n\t\t\tConnectionString: s.connectionString(),\n\t\t})\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t} else {\n\t\tlog.Println(\"Recovered from log\")\n\t}\n\n\tlog.Println(\"Initializing HTTP server\")\n\n\t// Initialize and start HTTP server.\n\ts.httpServer = &http.Server{\n\t\tAddr:    fmt.Sprintf(\":%d\", s.port),\n\t\tHandler: s.router,\n\t}\n\n\ts.router.HandleFunc(\"/db/{key}\", s.readHandler).Methods(\"GET\")\n\ts.router.HandleFunc(\"/db/{key}\", s.writeHandler).Methods(\"POST\")\n\ts.router.HandleFunc(\"/join\", s.joinHandler).Methods(\"POST\")\n\n\tlog.Println(\"Listening at:\", s.connectionString())\n\n\treturn s.httpServer.ListenAndServe()\n}\n\n// This is a hack around Gorilla mux not providing the correct net/http\n// HandleFunc() interface.\nfunc (s *Server) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {\n\ts.router.HandleFunc(pattern, handler)\n}\n\n// Joins to the leader of an existing cluster.\nfunc (s *Server) Join(leader string) error {\n\tcommand := &raft.DefaultJoinCommand{\n\t\tName:             s.raftServer.Name(),\n\t\tConnectionString: s.connectionString(),\n\t}\n\n\tvar b bytes.Buffer\n\tjson.NewEncoder(&b).Encode(command)\n\tresp, err := http.Post(fmt.Sprintf(\"http://%s/join\", leader), \"application/json\", &b)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp.Body.Close()\n\n\treturn nil\n}\n\nfunc (s *Server) joinHandler(w http.ResponseWriter, req *http.Request) {\n\tcommand := &raft.DefaultJoinCommand{}\n\n\tif err := json.NewDecoder(req.Body).Decode(&command); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tif _, err := s.raftServer.Do(command); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n}\n\nfunc (s *Server) readHandler(w http.ResponseWriter, req *http.Request) {\n\tvars := mux.Vars(req)\n\tvalue := s.db.Get(vars[\"key\"])\n\tw.Write([]byte(value))\n}\n\nfunc (s *Server) writeHandler(w http.ResponseWriter, req *http.Request) {\n\tvars := mux.Vars(req)\n\n\t// Read the value from the POST body.\n\tb, err := ioutil.ReadAll(req.Body)\n\tif err != nil {\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\tvalue := string(b)\n\n\t// Execute the command against the Raft server.\n\t_, err = s.raftServer.Do(command.NewWriteCommand(vars[\"key\"], value))\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t}\n}\n"
  }
]