Full Code of goraft/raftd for AI

master 404acbea0f48 cached
7 files
13.0 KB
3.6k tokens
19 symbols
1 requests
Download .txt
Repository: goraft/raftd
Branch: master
Commit: 404acbea0f48
Files: 7
Total size: 13.0 KB

Directory structure:
gitextract_qomad0nw/

├── .gitignore
├── LICENSE
├── README.md
├── command/
│   └── write_command.go
├── db/
│   └── db.go
├── main.go
└── server/
    └── server.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe


================================================
FILE: LICENSE
================================================
Copyright 2013 raftd contributors

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
raftd
=====

![unmaintained](http://img.shields.io/badge/status-unmaintained-red.png)

**NOTE**: This project is unmaintained. If you are using goraft in a project
and want to carry the project forward please file an issue with your ideas and
intentions. The original project authors have created new raft implementations
now used in [etcd](https://godoc.org/github.com/coreos/etcd/raft) and [InfluxDB](https://godoc.org/github.com/influxdb/influxdb/raft).

If you want to see a simple raft key-value store see the [etcd/raft example](https://github.com/coreos/etcd/tree/master/contrib/raftexample).

## Overview

The raftd server is a reference implementation for using the [goraft](https://github.com/goraft/raft) library.
This 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).
This protocol is based on Paxos but is architected to be more understandable.
It 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).

This reference implementation is very simple.
It is a key/value database with the following HTTP API:

```
# Set the value of a key.
$ curl -X POST http://localhost:4001/db/my_key -d 'FOO'
```

```
# Retrieve the value for a given key.
$ curl http://localhost:4001/db/my_key
FOO
```

All the values sent to the leader will be propagated to the other servers in the cluster.
This reference implementation does not support command forwarding.
If you try to send a change to a follower then it will simply be denied.


## Running

First, install raftd:

```sh
$ go get github.com/goraft/raftd
```

To start the first node in your cluster, simply specify a port and a directory where the data will be stored:

```sh
$ raftd -p 4001 ~/node.1
```

To add nodes to the cluster, you'll need to start on a different port and use a different data directory.
You'll also need to specify the host/port of the leader of the cluster to join:

```sh
$ raftd -p 4002 -join localhost:4001 ~/node.2
```

When you restart the node, it's already been joined to the cluster so you can remove the `-join` argument.

Finally, you can add one more node:

```sh
$ raftd -p 4003 -join localhost:4001 ~/node.3
```

Now when you set values to the leader:

```sh
$ curl -XPOST localhost:4001/db/foo -d 'bar'
```

The values will be propagated to the followers:

```sh
$ curl localhost:4001/db/foo
bar
$ curl localhost:4002/db/foo
bar
$ curl localhost:4003/db/foo
bar
```

Killing the leader will automatically elect a new leader.
If you kill and restart the first node and try to set a value you'll receive:

```sh
$ curl -XPOST localhost:4001/db/foo -d 'bar'
raft.Server: Not current leader
```

Leader forwarding is not implemented in the reference implementation.


## FAQ

### Why is command forwarding not implemented?

Command 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.

### Why isn't feature X implemented?

Raftd 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.


## Debugging

If you want to see more detail then you can specify several options for logging:

```
-v       Enables verbose raftd logging.
-debug   Enables debug-level raft logging.
-trace   Enables trace-level raft logging.
```

If you're having an issue getting `raftd` running, the `-debug` and `-trace` options can be really useful.


## Caveats

One 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.
So 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).
You will need to add additional nodes to allow failures to not affect the system.
For example, with 3 nodes you can have 1 node fail.
With 5 nodes you can have 2 nodes fail.



================================================
FILE: command/write_command.go
================================================
package command

import (
	"github.com/goraft/raft"
	"github.com/goraft/raftd/db"
)

// This command writes a value to a key.
type WriteCommand struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

// Creates a new write command.
func NewWriteCommand(key string, value string) *WriteCommand {
	return &WriteCommand{
		Key:   key,
		Value: value,
	}
}

// The name of the command in the log.
func (c *WriteCommand) CommandName() string {
	return "write"
}

// Writes a value to a key.
func (c *WriteCommand) Apply(server raft.Server) (interface{}, error) {
	db := server.Context().(*db.DB)
	db.Put(c.Key, c.Value)
	return nil, nil
}


================================================
FILE: db/db.go
================================================
package db

import (
	"sync"
)

// The key-value database.
type DB struct {
	data  map[string]string
	mutex sync.RWMutex
}

// Creates a new database.
func New() *DB {
	return &DB{
		data: make(map[string]string),
	}
}

// Retrieves the value for a given key.
func (db *DB) Get(key string) string {
	db.mutex.RLock()
	defer db.mutex.RUnlock()
	return db.data[key]
}

// Sets the value for a given key.
func (db *DB) Put(key string, value string) {
	db.mutex.Lock()
	defer db.mutex.Unlock()
	db.data[key] = value
}


================================================
FILE: main.go
================================================
package main

import (
	"flag"
	"fmt"
	"github.com/goraft/raft"
	"github.com/goraft/raftd/command"
	"github.com/goraft/raftd/server"
	"log"
	"math/rand"
	"os"
	"time"
)

var verbose bool
var trace bool
var debug bool
var host string
var port int
var join string

func init() {
	flag.BoolVar(&verbose, "v", false, "verbose logging")
	flag.BoolVar(&trace, "trace", false, "Raft trace debugging")
	flag.BoolVar(&debug, "debug", false, "Raft debugging")
	flag.StringVar(&host, "h", "localhost", "hostname")
	flag.IntVar(&port, "p", 4001, "port")
	flag.StringVar(&join, "join", "", "host:port of leader to join")
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [arguments] <data-path> \n", os.Args[0])
		flag.PrintDefaults()
	}
}

func main() {
	log.SetFlags(0)
	flag.Parse()
	if verbose {
		log.Print("Verbose logging enabled.")
	}
	if trace {
		raft.SetLogLevel(raft.Trace)
		log.Print("Raft trace debugging enabled.")
	} else if debug {
		raft.SetLogLevel(raft.Debug)
		log.Print("Raft debugging enabled.")
	}

	rand.Seed(time.Now().UnixNano())

	// Setup commands.
	raft.RegisterCommand(&command.WriteCommand{})

	// Set the data directory.
	if flag.NArg() == 0 {
		flag.Usage()
		log.Fatal("Data path argument required")
	}
	path := flag.Arg(0)
	if err := os.MkdirAll(path, 0744); err != nil {
		log.Fatalf("Unable to create path: %v", err)
	}

	log.SetFlags(log.LstdFlags)
	s := server.New(path, host, port)
	log.Fatal(s.ListenAndServe(join))
}


================================================
FILE: server/server.go
================================================
package server

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/goraft/raft"
	"github.com/goraft/raftd/command"
	"github.com/goraft/raftd/db"
	"github.com/gorilla/mux"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"path/filepath"
	"sync"
	"time"
)

// The raftd server is a combination of the Raft server and an HTTP
// server which acts as the transport.
type Server struct {
	name       string
	host       string
	port       int
	path       string
	router     *mux.Router
	raftServer raft.Server
	httpServer *http.Server
	db         *db.DB
	mutex      sync.RWMutex
}

// Creates a new server.
func New(path string, host string, port int) *Server {
	s := &Server{
		host:   host,
		port:   port,
		path:   path,
		db:     db.New(),
		router: mux.NewRouter(),
	}

	// Read existing name or generate a new one.
	if b, err := ioutil.ReadFile(filepath.Join(path, "name")); err == nil {
		s.name = string(b)
	} else {
		s.name = fmt.Sprintf("%07x", rand.Int())[0:7]
		if err = ioutil.WriteFile(filepath.Join(path, "name"), []byte(s.name), 0644); err != nil {
			panic(err)
		}
	}

	return s
}

// Returns the connection string.
func (s *Server) connectionString() string {
	return fmt.Sprintf("http://%s:%d", s.host, s.port)
}

// Starts the server.
func (s *Server) ListenAndServe(leader string) error {
	var err error

	log.Printf("Initializing Raft Server: %s", s.path)

	// Initialize and start Raft server.
	transporter := raft.NewHTTPTransporter("/raft", 200*time.Millisecond)
	s.raftServer, err = raft.NewServer(s.name, s.path, transporter, nil, s.db, "")
	if err != nil {
		log.Fatal(err)
	}
	transporter.Install(s.raftServer, s)
	s.raftServer.Start()

	if leader != "" {
		// Join to leader if specified.

		log.Println("Attempting to join leader:", leader)

		if !s.raftServer.IsLogEmpty() {
			log.Fatal("Cannot join with an existing log")
		}
		if err := s.Join(leader); err != nil {
			log.Fatal(err)
		}

	} else if s.raftServer.IsLogEmpty() {
		// Initialize the server by joining itself.

		log.Println("Initializing new cluster")

		_, err := s.raftServer.Do(&raft.DefaultJoinCommand{
			Name:             s.raftServer.Name(),
			ConnectionString: s.connectionString(),
		})
		if err != nil {
			log.Fatal(err)
		}

	} else {
		log.Println("Recovered from log")
	}

	log.Println("Initializing HTTP server")

	// Initialize and start HTTP server.
	s.httpServer = &http.Server{
		Addr:    fmt.Sprintf(":%d", s.port),
		Handler: s.router,
	}

	s.router.HandleFunc("/db/{key}", s.readHandler).Methods("GET")
	s.router.HandleFunc("/db/{key}", s.writeHandler).Methods("POST")
	s.router.HandleFunc("/join", s.joinHandler).Methods("POST")

	log.Println("Listening at:", s.connectionString())

	return s.httpServer.ListenAndServe()
}

// This is a hack around Gorilla mux not providing the correct net/http
// HandleFunc() interface.
func (s *Server) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
	s.router.HandleFunc(pattern, handler)
}

// Joins to the leader of an existing cluster.
func (s *Server) Join(leader string) error {
	command := &raft.DefaultJoinCommand{
		Name:             s.raftServer.Name(),
		ConnectionString: s.connectionString(),
	}

	var b bytes.Buffer
	json.NewEncoder(&b).Encode(command)
	resp, err := http.Post(fmt.Sprintf("http://%s/join", leader), "application/json", &b)
	if err != nil {
		return err
	}
	resp.Body.Close()

	return nil
}

func (s *Server) joinHandler(w http.ResponseWriter, req *http.Request) {
	command := &raft.DefaultJoinCommand{}

	if err := json.NewDecoder(req.Body).Decode(&command); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if _, err := s.raftServer.Do(command); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (s *Server) readHandler(w http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	value := s.db.Get(vars["key"])
	w.Write([]byte(value))
}

func (s *Server) writeHandler(w http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)

	// Read the value from the POST body.
	b, err := ioutil.ReadAll(req.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	value := string(b)

	// Execute the command against the Raft server.
	_, err = s.raftServer.Do(command.NewWriteCommand(vars["key"], value))
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
	}
}
Download .txt
gitextract_qomad0nw/

├── .gitignore
├── LICENSE
├── README.md
├── command/
│   └── write_command.go
├── db/
│   └── db.go
├── main.go
└── server/
    └── server.go
Download .txt
SYMBOL INDEX (19 symbols across 4 files)

FILE: command/write_command.go
  type WriteCommand (line 9) | type WriteCommand struct
    method CommandName (line 23) | func (c *WriteCommand) CommandName() string {
    method Apply (line 28) | func (c *WriteCommand) Apply(server raft.Server) (interface{}, error) {
  function NewWriteCommand (line 15) | func NewWriteCommand(key string, value string) *WriteCommand {

FILE: db/db.go
  type DB (line 8) | type DB struct
    method Get (line 21) | func (db *DB) Get(key string) string {
    method Put (line 28) | func (db *DB) Put(key string, value string) {
  function New (line 14) | func New() *DB {

FILE: main.go
  function init (line 22) | func init() {
  function main (line 35) | func main() {

FILE: server/server.go
  type Server (line 22) | type Server struct
    method connectionString (line 58) | func (s *Server) connectionString() string {
    method ListenAndServe (line 63) | func (s *Server) ListenAndServe(leader string) error {
    method HandleFunc (line 125) | func (s *Server) HandleFunc(pattern string, handler func(http.Response...
    method Join (line 130) | func (s *Server) Join(leader string) error {
    method joinHandler (line 147) | func (s *Server) joinHandler(w http.ResponseWriter, req *http.Request) {
    method readHandler (line 160) | func (s *Server) readHandler(w http.ResponseWriter, req *http.Request) {
    method writeHandler (line 166) | func (s *Server) writeHandler(w http.ResponseWriter, req *http.Request) {
  function New (line 35) | func New(path string, host string, port int) *Server {
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (14K chars).
[
  {
    "path": ".gitignore",
    "chars": 252,
    "preview": "# 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 spe"
  },
  {
    "path": "LICENSE",
    "chars": 1058,
    "preview": "Copyright 2013 raftd contributors\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this "
  },
  {
    "path": "README.md",
    "chars": 4970,
    "preview": "raftd\n=====\n\n![unmaintained](http://img.shields.io/badge/status-unmaintained-red.png)\n\n**NOTE**: This project is unmaint"
  },
  {
    "path": "command/write_command.go",
    "chars": 646,
    "preview": "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"
  },
  {
    "path": "db/db.go",
    "chars": 514,
    "preview": "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"
  },
  {
    "path": "main.go",
    "chars": 1457,
    "preview": "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/r"
  },
  {
    "path": "server/server.go",
    "chars": 4406,
    "preview": "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"
  }
]

About this extraction

This page contains the full source code of the goraft/raftd GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (13.0 KB), approximately 3.6k tokens, and a symbol index with 19 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!