mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
268 lines
6.7 KiB
Go
268 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"github.com/benbjohnson/go-raft"
|
|
"github.com/gorilla/mux"
|
|
"log"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strings"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Initialization
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
var verbose bool
|
|
|
|
func init() {
|
|
flag.BoolVar(&verbose, "v", false, "verbose logging")
|
|
flag.BoolVar(&verbose, "verbose", false, "verbose logging")
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Typedefs
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
type Info struct {
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Variables
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
var server *raft.Server
|
|
var logger *log.Logger
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Functions
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
//--------------------------------------
|
|
// Main
|
|
//--------------------------------------
|
|
|
|
func main() {
|
|
var err error
|
|
logger = log.New(os.Stdout, "", log.LstdFlags)
|
|
flag.Parse()
|
|
if verbose {
|
|
fmt.Println("Verbose logging enabled.\n")
|
|
}
|
|
|
|
// Setup commands.
|
|
raft.RegisterCommand(&JoinCommand{})
|
|
raft.RegisterCommand(&SetCommand{})
|
|
raft.RegisterCommand(&GetCommand{})
|
|
raft.RegisterCommand(&DeleteCommand{})
|
|
|
|
// Use the present working directory if a directory was not passed in.
|
|
var path string
|
|
if flag.NArg() == 0 {
|
|
path, _ = os.Getwd()
|
|
} else {
|
|
path = flag.Arg(0)
|
|
if err := os.MkdirAll(path, 0744); err != nil {
|
|
fatal("Unable to create path: %v", err)
|
|
}
|
|
}
|
|
|
|
// Read server info from file or grab it from user.
|
|
var info *Info = getInfo(path)
|
|
name := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
|
fmt.Printf("Name: %s\n\n", name)
|
|
|
|
t := transHandler{}
|
|
|
|
// Setup new raft server.
|
|
server, err = raft.NewServer(name, path, t, nil)
|
|
//server.DoHandler = DoHandler;
|
|
if err != nil {
|
|
fatal("%v", err)
|
|
}
|
|
server.Initialize()
|
|
fmt.Println("1 join as ", server.State(), " term ", server.Term())
|
|
// Join to another server if we don't have a log.
|
|
if server.IsLogEmpty() {
|
|
var leaderHost string
|
|
fmt.Println("2 join as ", server.State(), " term ", server.Term())
|
|
fmt.Println("This server has no log. Please enter a server in the cluster to join\nto or hit enter to initialize a cluster.");
|
|
fmt.Printf("Join to (host:port)> ");
|
|
fmt.Scanf("%s", &leaderHost)
|
|
fmt.Println("3 join as ", server.State(), " term ", server.Term())
|
|
if leaderHost == "" {
|
|
fmt.Println("init")
|
|
server.SetElectionTimeout(1 * time.Second)
|
|
server.SetHeartbeatTimeout(1 * time.Second)
|
|
server.StartLeader()
|
|
} else {
|
|
server.SetElectionTimeout(1 * time.Second)
|
|
server.SetHeartbeatTimeout(1 * time.Second)
|
|
server.StartFollower()
|
|
fmt.Println("4 join as ", server.State(), " term ", server.Term())
|
|
Join(server, leaderHost)
|
|
fmt.Println("success join")
|
|
}
|
|
}
|
|
// open snapshot
|
|
//go server.Snapshot()
|
|
|
|
// Create HTTP interface.
|
|
r := mux.NewRouter()
|
|
|
|
// internal commands
|
|
r.HandleFunc("/join", JoinHttpHandler).Methods("POST")
|
|
r.HandleFunc("/vote", VoteHttpHandler).Methods("POST")
|
|
r.HandleFunc("/log", GetLogHttpHandler).Methods("GET")
|
|
r.HandleFunc("/log/append", AppendEntriesHttpHandler).Methods("POST")
|
|
r.HandleFunc("/snapshot", SnapshotHttpHandler).Methods("POST")
|
|
|
|
// external commands
|
|
r.HandleFunc("/set/{key}", SetHttpHandler).Methods("POST")
|
|
r.HandleFunc("/get/{key}", GetHttpHandler).Methods("GET")
|
|
r.HandleFunc("/delete/{key}", DeleteHttpHandler).Methods("GET")
|
|
r.HandleFunc("/watch/{key}", WatchHttpHandler).Methods("GET")
|
|
|
|
http.Handle("/", r)
|
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", info.Port), nil))
|
|
}
|
|
|
|
func usage() {
|
|
fatal("usage: raftd [PATH]")
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Config
|
|
//--------------------------------------
|
|
|
|
func getInfo(path string) *Info {
|
|
info := &Info{}
|
|
|
|
// Read in the server info if available.
|
|
infoPath := fmt.Sprintf("%s/info", path)
|
|
if file, err := os.Open(infoPath); err == nil {
|
|
if content, err := ioutil.ReadAll(file); err != nil {
|
|
fatal("Unable to read info: %v", err)
|
|
} else {
|
|
if err = json.Unmarshal(content, &info); err != nil {
|
|
fatal("Unable to parse info: %v", err)
|
|
}
|
|
}
|
|
file.Close()
|
|
|
|
// Otherwise ask user for info and write it to file.
|
|
} else {
|
|
fmt.Printf("Enter hostname: [localhost] ");
|
|
fmt.Scanf("%s", &info.Host)
|
|
info.Host = strings.TrimSpace(info.Host)
|
|
if info.Host == "" {
|
|
info.Host = "localhost"
|
|
}
|
|
|
|
fmt.Printf("Enter port: [4001] ");
|
|
fmt.Scanf("%d", &info.Port)
|
|
if info.Port == 0 {
|
|
info.Port = 4001
|
|
}
|
|
|
|
// Write to file.
|
|
content, _ := json.Marshal(info)
|
|
content = []byte(string(content) + "\n")
|
|
if err := ioutil.WriteFile(infoPath, content, 0644); err != nil {
|
|
fatal("Unable to write info to file: %v", err)
|
|
}
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
// Handlers
|
|
//--------------------------------------
|
|
|
|
// Send join requests to the leader.
|
|
func Join(s *raft.Server, serverName string) error {
|
|
var b bytes.Buffer
|
|
command := &JoinCommand{}
|
|
command.Name = s.Name()
|
|
|
|
json.NewEncoder(&b).Encode(command)
|
|
debug("[send] POST http://%v/join", "localhost:4001")
|
|
resp, err := http.Post(fmt.Sprintf("http://%s/join", serverName), "application/json", &b)
|
|
if resp != nil {
|
|
resp.Body.Close()
|
|
if resp.StatusCode == http.StatusOK {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("raftd: Unable to join: %v", err)
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
// HTTP Utilities
|
|
//--------------------------------------
|
|
|
|
func decodeJsonRequest(req *http.Request, data interface{}) error {
|
|
decoder := json.NewDecoder(req.Body)
|
|
if err := decoder.Decode(&data); err != nil && err != io.EOF {
|
|
logger.Println("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)
|
|
}
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Log
|
|
//--------------------------------------
|
|
|
|
func debug(msg string, v ...interface{}) {
|
|
if verbose {
|
|
logger.Printf("DEBUG " + msg + "\n", v...)
|
|
}
|
|
}
|
|
|
|
func info(msg string, v ...interface{}) {
|
|
logger.Printf("INFO " + msg + "\n", v...)
|
|
}
|
|
|
|
func warn(msg string, v ...interface{}) {
|
|
logger.Printf("WARN " + msg + "\n", v...)
|
|
}
|
|
|
|
func fatal(msg string, v ...interface{}) {
|
|
logger.Printf("FATAL " + msg + "\n", v...)
|
|
os.Exit(1)
|
|
}
|
|
|
|
|
|
|