forked from hashicorp/nomad
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is the main patch for hashicorp#117. It still has several outstanding problems that we'll work through in later commits, but it's good enough to discuss.
- Loading branch information
Showing
22 changed files
with
1,023 additions
and
811 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package raft | ||
|
||
import "fmt" | ||
|
||
// ServerSuffrage determines whether a Server in a Configuration gets a vote. | ||
type ServerSuffrage int | ||
|
||
const ( | ||
// Voter is a server whose vote is counted in elections and whose match index | ||
// is used in advancing the leader's commit index. | ||
Voter ServerSuffrage = 1 | ||
// Nonvoter is a server that receives log entries but is not considered for | ||
// elections or commitment purposes. | ||
Nonvoter ServerSuffrage = 2 | ||
// Staging is a server that acts like a nonvoter with one exception: once a | ||
// staging server receives enough log entries to be sufficiently caught up to | ||
// the leader's log, the leader will invoke a membership change to change | ||
// the Staging server to a Voter. | ||
Staging ServerSuffrage = 3 | ||
) | ||
|
||
// Server tracks the information about a single server in a configuration. | ||
type Server struct { | ||
// Suffrage determines whether the server gets a vote. | ||
Suffrage ServerSuffrage | ||
// GUID is a unique string identifying this server for all time. | ||
GUID string | ||
// Address is its network address that a transport can contact. | ||
Address string | ||
} | ||
|
||
// Configuration tracks which servers are in the cluster, and whether they have | ||
// votes. This should include the local server, if it's a member of the cluster. | ||
// The servers are listed no particular order, but each should only appear once. | ||
// These entries are appended to the log during membership changes. | ||
type Configuration struct { | ||
Servers []Server | ||
} | ||
|
||
// configurations is state tracked on every server about its Configurations. | ||
// Note that, per Diego's dissertation, there can be at most one uncommitted | ||
// configuration at a time (the next configuration may not be created until the | ||
// prior one has been committed). | ||
// | ||
// One downside to storing just two configurations is that if you try to take a | ||
// snahpsot when your state machine hasn't yet applied the committedIndex, we | ||
// have no record of the configuration that would logically fit into that | ||
// snapshot. We disallow snapshots in that case now. An alternative approach, | ||
// which LogCabin uses, is to track every configuration change in the | ||
// log. | ||
type configurations struct { | ||
// committed is the latest configuration in the log/snapshot that has been | ||
// committed (the one with the largest index). | ||
committed Configuration | ||
// committedIndex is the log index where 'committed' was written. | ||
committedIndex uint64 | ||
// latest is the latest configuration in the log/snapshot (may be committed | ||
// or uncommitted) | ||
latest Configuration | ||
// latestIndex is the log index where 'latest' was written. | ||
latestIndex uint64 | ||
} | ||
|
||
// cloneConfiguration makes a deep copy of a Configuration. | ||
func cloneConfiguration(old Configuration) (copy Configuration) { | ||
copy.Servers = append(copy.Servers, old.Servers...) | ||
return | ||
} | ||
|
||
// hasVote returns true if the server identified by 'guid' is a Voter in the | ||
// provided Configuration. | ||
func hasVote(configuration Configuration, guid string) bool { | ||
for _, server := range configuration.Servers { | ||
if server.GUID == guid { | ||
return server.Suffrage == Voter | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// checkConfiguration tests a cluster membership configuration for common | ||
// errors. | ||
func checkConfiguration(configuration Configuration) error { | ||
guidSet := make(map[string]bool) | ||
addressSet := make(map[string]bool) | ||
var voters int | ||
for _, server := range configuration.Servers { | ||
if server.GUID == "" { | ||
return fmt.Errorf("Empty GUID in configuration: %v", configuration) | ||
} | ||
if server.Address == "" { | ||
return fmt.Errorf("Empty address in configuration: %v", server) | ||
} | ||
if guidSet[server.GUID] { | ||
return fmt.Errorf("Found duplicate GUID in configuration: %v", server.GUID) | ||
} | ||
guidSet[server.GUID] = true | ||
if addressSet[server.Address] { | ||
return fmt.Errorf("Found duplicate Address in configuration: %v", server.Address) | ||
} | ||
addressSet[server.Address] = true | ||
if server.Suffrage == Voter { | ||
voters++ | ||
} | ||
} | ||
if voters == 0 { | ||
return fmt.Errorf("Need at least one voter in configuration: %v", configuration) | ||
} | ||
return nil | ||
} | ||
|
||
// decodePeers is used to deserialize an old list of peers into a Configuration. | ||
// This is here for backwards compatibility with old log entries and snapshots; | ||
// it should be removed eventually. | ||
func decodePeers(buf []byte, trans Transport) Configuration { | ||
// Decode the buffer first | ||
var encPeers [][]byte | ||
if err := decodeMsgPack(buf, &encPeers); err != nil { | ||
panic(fmt.Errorf("failed to decode peers: %v", err)) | ||
} | ||
|
||
// Deserialize each peer | ||
var servers []Server | ||
for _, enc := range encPeers { | ||
p := trans.DecodePeer(enc) | ||
servers = append(servers, Server{ | ||
Suffrage: Voter, | ||
GUID: p, | ||
Address: p, | ||
}) | ||
} | ||
|
||
return Configuration{ | ||
Servers: servers, | ||
} | ||
} | ||
|
||
// encodeConfiguration serializes a Configuration using MsgPack, or panics on | ||
// errors. | ||
func encodeConfiguration(configuration Configuration) []byte { | ||
buf, err := encodeMsgPack(configuration) | ||
if err != nil { | ||
panic(fmt.Errorf("failed to encode peers: %v", err)) | ||
} | ||
return buf.Bytes() | ||
} | ||
|
||
// decodeConfiguration deserializes a Configuration using MsgPack, or panics on | ||
// errors. | ||
func decodeConfiguration(buf []byte) Configuration { | ||
var configuration Configuration | ||
if err := decodeMsgPack(buf, &configuration); err != nil { | ||
panic(fmt.Errorf("failed to decode configuration: %v", err)) | ||
} | ||
return configuration | ||
} |
Oops, something went wrong.