wip/ move write/read to server.go and add test
parent
ce1bd81d08
commit
ff29c6d17d
83
config.go
83
config.go
|
@ -1,84 +1,7 @@
|
||||||
package raft
|
package raft
|
||||||
|
|
||||||
import (
|
type Config struct {
|
||||||
"encoding/json"
|
CommitIndex uint64 `json:"commitIndex"`
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RaftConfig struct {
|
|
||||||
KnownCommitIndex uint64 `json:"KnownCommitIndex"`
|
|
||||||
// TODO decide what we need to store in peer struct
|
// TODO decide what we need to store in peer struct
|
||||||
Peers []string `json:"Peers"`
|
Peers []string `json:"peers"`
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) writeConf() {
|
|
||||||
|
|
||||||
peers := make([]string, len(s.peers))
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
for peer := range s.peers {
|
|
||||||
peers[i] = peer
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
r := &RaftConfig{
|
|
||||||
KnownCommitIndex: s.log.commitIndex,
|
|
||||||
Peers: peers,
|
|
||||||
}
|
|
||||||
|
|
||||||
b, _ := json.Marshal(r)
|
|
||||||
|
|
||||||
confBakPath := path.Join(s.path, "conf.bak")
|
|
||||||
confPath := path.Join(s.path, "conf")
|
|
||||||
|
|
||||||
confFile, err := os.OpenFile(confBakPath, os.O_WRONLY|os.O_CREATE, 0600)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
confFile.Write(b)
|
|
||||||
|
|
||||||
os.Remove(confPath)
|
|
||||||
os.Rename(confBakPath, confPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the configuration for the server.
|
|
||||||
func (s *Server) readConf() error {
|
|
||||||
confPath := path.Join(s.path, "conf")
|
|
||||||
s.debugln("readConf.open ", confPath)
|
|
||||||
// open conf file
|
|
||||||
confFile, err := os.OpenFile(confPath, os.O_RDWR, 0600)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
_, err = os.OpenFile(confPath, os.O_WRONLY|os.O_CREATE, 0600)
|
|
||||||
debugln("readConf.create ", confPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
raftConf := &RaftConfig{}
|
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(confFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
confFile.Close()
|
|
||||||
|
|
||||||
err = json.Unmarshal(b, raftConf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.commitIndex = raftConf.KnownCommitIndex
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
64
server.go
64
server.go
|
@ -927,7 +927,7 @@ func (s *Server) processRequestVoteRequest(req *RequestVoteRequest) (*RequestVot
|
||||||
// Adds a peer to the server.
|
// Adds a peer to the server.
|
||||||
func (s *Server) AddPeer(name string) error {
|
func (s *Server) AddPeer(name string) error {
|
||||||
s.debugln("server.peer.add: ", name, len(s.peers))
|
s.debugln("server.peer.add: ", name, len(s.peers))
|
||||||
|
defer s.writeConf()
|
||||||
// Do not allow peers to be added twice.
|
// Do not allow peers to be added twice.
|
||||||
if s.peers[name] != nil {
|
if s.peers[name] != nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -946,7 +946,6 @@ func (s *Server) AddPeer(name string) error {
|
||||||
|
|
||||||
s.peers[peer.name] = peer
|
s.peers[peer.name] = peer
|
||||||
|
|
||||||
s.writeConf()
|
|
||||||
s.debugln("server.peer.conf.write: ", name)
|
s.debugln("server.peer.conf.write: ", name)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -956,11 +955,12 @@ func (s *Server) AddPeer(name string) error {
|
||||||
func (s *Server) RemovePeer(name string) error {
|
func (s *Server) RemovePeer(name string) error {
|
||||||
s.debugln("server.peer.remove: ", name, len(s.peers))
|
s.debugln("server.peer.remove: ", name, len(s.peers))
|
||||||
|
|
||||||
|
defer s.writeConf()
|
||||||
|
|
||||||
if name == s.Name() {
|
if name == s.Name() {
|
||||||
// when the removed node restart, it should be able
|
// when the removed node restart, it should be able
|
||||||
// to know it has been removed before. So we need
|
// to know it has been removed before. So we need
|
||||||
// to update knownCommitIndex
|
// to update knownCommitIndex
|
||||||
s.writeConf()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Return error if peer doesn't exist.
|
// Return error if peer doesn't exist.
|
||||||
|
@ -976,8 +976,6 @@ func (s *Server) RemovePeer(name string) error {
|
||||||
|
|
||||||
delete(s.peers, name)
|
delete(s.peers, name)
|
||||||
|
|
||||||
s.writeConf()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1208,6 +1206,62 @@ func (s *Server) LoadSnapshot() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------
|
||||||
|
// Config File
|
||||||
|
//--------------------------------------
|
||||||
|
|
||||||
|
func (s *Server) writeConf() {
|
||||||
|
|
||||||
|
peers := make([]string, len(s.peers))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for peer, _ := range s.peers {
|
||||||
|
peers[i] = peer
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &Config{
|
||||||
|
CommitIndex: s.log.commitIndex,
|
||||||
|
Peers: peers,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, _ := json.Marshal(r)
|
||||||
|
|
||||||
|
confPath := path.Join(s.path, "conf")
|
||||||
|
tmpConfPath := path.Join(s.path, "conf.tmp")
|
||||||
|
|
||||||
|
err := ioutil.WriteFile(tmpConfPath, b, 0600)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Rename(tmpConfPath, confPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the configuration for the server.
|
||||||
|
func (s *Server) readConf() error {
|
||||||
|
confPath := path.Join(s.path, "conf")
|
||||||
|
s.debugln("readConf.open ", confPath)
|
||||||
|
|
||||||
|
// open conf file
|
||||||
|
b, err := ioutil.ReadFile(confPath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := &Config{}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(b, conf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.commitIndex = conf.CommitIndex
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------
|
//--------------------------------------
|
||||||
// Debugging
|
// Debugging
|
||||||
//--------------------------------------
|
//--------------------------------------
|
||||||
|
|
118
server_test.go
118
server_test.go
|
@ -316,6 +316,124 @@ func TestServerDenyCommandExecutionWhenFollower(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------
|
||||||
|
// Recovery
|
||||||
|
//--------------------------------------
|
||||||
|
|
||||||
|
// Ensure that a follower cannot execute a command.
|
||||||
|
func TestServerRecoverFromPreviousLogAndConf(t *testing.T) {
|
||||||
|
// Initialize the servers.
|
||||||
|
var mutex sync.RWMutex
|
||||||
|
servers := map[string]*Server{}
|
||||||
|
|
||||||
|
transporter := &testTransporter{}
|
||||||
|
transporter.sendVoteRequestFunc = func(server *Server, peer *Peer, req *RequestVoteRequest) *RequestVoteResponse {
|
||||||
|
mutex.RLock()
|
||||||
|
s := servers[peer.name]
|
||||||
|
mutex.RUnlock()
|
||||||
|
return s.RequestVote(req)
|
||||||
|
}
|
||||||
|
transporter.sendAppendEntriesRequestFunc = func(server *Server, peer *Peer, req *AppendEntriesRequest) *AppendEntriesResponse {
|
||||||
|
mutex.RLock()
|
||||||
|
s := servers[peer.name]
|
||||||
|
mutex.RUnlock()
|
||||||
|
return s.AppendEntries(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
disTransporter := &testTransporter{}
|
||||||
|
disTransporter.sendVoteRequestFunc = func(server *Server, peer *Peer, req *RequestVoteRequest) *RequestVoteResponse {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
disTransporter.sendAppendEntriesRequestFunc = func(server *Server, peer *Peer, req *AppendEntriesRequest) *AppendEntriesResponse {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var names []string
|
||||||
|
var paths = make(map[string]string)
|
||||||
|
|
||||||
|
n := 5
|
||||||
|
|
||||||
|
// add n servers
|
||||||
|
for i := 1; i <= n; i++ {
|
||||||
|
names = append(names, strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
var leader *Server
|
||||||
|
for _, name := range names {
|
||||||
|
server := newTestServer(name, transporter)
|
||||||
|
|
||||||
|
servers[name] = server
|
||||||
|
paths[name] = server.Path()
|
||||||
|
|
||||||
|
if name == "1" {
|
||||||
|
leader = server
|
||||||
|
server.SetHeartbeatTimeout(testHeartbeatTimeout)
|
||||||
|
server.Start()
|
||||||
|
time.Sleep(testHeartbeatTimeout)
|
||||||
|
} else {
|
||||||
|
server.SetElectionTimeout(testElectionTimeout)
|
||||||
|
server.SetHeartbeatTimeout(testHeartbeatTimeout)
|
||||||
|
server.Start()
|
||||||
|
time.Sleep(testHeartbeatTimeout)
|
||||||
|
}
|
||||||
|
if _, err := leader.Do(&DefaultJoinCommand{Name: name}); err != nil {
|
||||||
|
t.Fatalf("Unable to join server[%s]: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit some commands
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
if _, err := leader.Do(&testCommand2{X: 1}); err != nil {
|
||||||
|
t.Fatalf("cannot commit command:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(2 * testHeartbeatTimeout)
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
server := servers[name]
|
||||||
|
if server.CommitIndex() != 16 {
|
||||||
|
t.Fatalf("%s commitIndex is invalid [%d/%d]", name, server.CommitIndex(), 16)
|
||||||
|
}
|
||||||
|
server.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
// with old path and disable transportation
|
||||||
|
server := newTestServerWithPath(name, disTransporter, paths[name])
|
||||||
|
servers[name] = server
|
||||||
|
|
||||||
|
server.Start()
|
||||||
|
|
||||||
|
// should only commit to the last join command
|
||||||
|
if server.CommitIndex() != 6 {
|
||||||
|
t.Fatalf("%s recover phase 1 commitIndex is invalid [%d/%d]", name, server.CommitIndex(), 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
// peer conf should be recovered
|
||||||
|
if len(server.Peers()) != 4 {
|
||||||
|
t.Fatalf("%s recover phase 1 peer failed! [%d/%d]", name, len(server.Peers()), 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let nodes talk to each other
|
||||||
|
for _, name := range names {
|
||||||
|
servers[name].SetTransporter(transporter)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(2 * testElectionTimeout)
|
||||||
|
|
||||||
|
// should commit to the previous index + 1(nop command when new leader elected)
|
||||||
|
for _, name := range names {
|
||||||
|
server := servers[name]
|
||||||
|
if server.CommitIndex() != 17 {
|
||||||
|
t.Fatalf("%s commitIndex is invalid [%d/%d]", name, server.CommitIndex(), 16)
|
||||||
|
}
|
||||||
|
server.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------
|
//--------------------------------------
|
||||||
// Membership
|
// Membership
|
||||||
//--------------------------------------
|
//--------------------------------------
|
||||||
|
|
5
test.go
5
test.go
|
@ -69,6 +69,11 @@ func newTestServer(name string, transporter Transporter) *Server {
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newTestServerWithPath(name string, transporter Transporter, p string) *Server {
|
||||||
|
server, _ := NewServer(name, p, transporter, nil, nil)
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
func newTestServerWithLog(name string, transporter Transporter, entries []*LogEntry) *Server {
|
func newTestServerWithLog(name string, transporter Transporter, entries []*LogEntry) *Server {
|
||||||
server := newTestServer(name, transporter)
|
server := newTestServer(name, transporter)
|
||||||
f, err := os.Create(server.LogPath())
|
f, err := os.Create(server.LogPath())
|
||||||
|
|
Loading…
Reference in New Issue