Move deps from _workspace/ to vendor/
godep restore pushd $GOPATH/src/github.com/appc/spec git co master popd go get go4.org/errorutil rm -rf Godeps godep save ./... git add vendor git add -f $(git ls-files --other vendor/) git co -- Godeps/LICENSES Godeps/.license_file_state Godeps/OWNERS
This commit is contained in:
57
vendor/github.com/coreos/etcd/raft/design.md
generated
vendored
Normal file
57
vendor/github.com/coreos/etcd/raft/design.md
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
## Progress
|
||||
|
||||
Progress represents a follower’s progress in the view of the leader. Leader maintains progresses of all followers, and sends `replication message` to the follower based on its progress.
|
||||
|
||||
`replication message` is a `msgApp` with log entries.
|
||||
|
||||
A progress has two attribute: `match` and `next`. `match` is the index of the highest known matched entry. If leader knows nothing about follower’s replication status, `match` is set to zero. `next` is the index of the first entry that will be replicated to the follower. Leader puts entries from `next` to its latest one in next `replication message`.
|
||||
|
||||
A progress is in one of the three state: `probe`, `replicate`, `snapshot`.
|
||||
|
||||
```
|
||||
+--------------------------------------------------------+
|
||||
| send snapshot |
|
||||
| |
|
||||
+---------+----------+ +----------v---------+
|
||||
+---> probe | | snapshot |
|
||||
| | max inflight = 1 <----------------------------------+ max inflight = 0 |
|
||||
| +---------+----------+ +--------------------+
|
||||
| | 1. snapshot success
|
||||
| | (next=snapshot.index + 1)
|
||||
| | 2. snapshot failure
|
||||
| | (no change)
|
||||
| | 3. receives msgAppResp(rej=false&&index>lastsnap.index)
|
||||
| | (match=m.index,next=match+1)
|
||||
receives msgAppResp(rej=true)
|
||||
(next=match+1)| |
|
||||
| |
|
||||
| |
|
||||
| | receives msgAppResp(rej=false&&index>match)
|
||||
| | (match=m.index,next=match+1)
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| +---------v----------+
|
||||
| | replicate |
|
||||
+---+ max inflight = n |
|
||||
+--------------------+
|
||||
```
|
||||
|
||||
When the progress of a follower is in `probe` state, leader sends at most one `replication message` per heartbeat interval. The leader sends `replication message` slowly and probing the actual progress of the follower. A `msgHeartbeatResp` or a `msgAppResp` with reject might trigger the sending of the next `replication message`.
|
||||
|
||||
When the progress of a follower is in `replicate` state, leader sends `replication message`, then optimistically increases `next` to the latest entry sent. This is an optimized state for fast replicating log entries to the follower.
|
||||
|
||||
When the progress of a follower is in `snapshot` state, leader stops sending any `replication message`.
|
||||
|
||||
A newly elected leader sets the progresses of all the followers to `probe` state with `match` = 0 and `next` = last index. The leader slowly (at most once per heartbeat) sends `replication message` to the follower and probes its progress.
|
||||
|
||||
A progress changes to `replicate` when the follower replies with a non-rejection `msgAppResp`, which implies that it has matched the index sent. At this point, leader starts to stream log entries to the follower fast. The progress will fall back to `probe` when the follower replies a rejection `msgAppResp` or the link layer reports the follower is unreachable. We aggressively reset `next` to `match`+1 since if we receive any `msgAppResp` soon, both `match` and `next` will increase directly to the `index` in `msgAppResp`. (We might end up with sending some duplicate entries when aggressively reset `next` too low. see open question)
|
||||
|
||||
A progress changes from `probe` to `snapshot` when the follower falls very far behind and requires a snapshot. After sending `msgSnap`, the leader waits until the success, failure or abortion of the previous snapshot sent. The progress will go back to `probe` after the sending result is applied.
|
||||
|
||||
### Flow Control
|
||||
|
||||
1. limit the max size of message sent per message. Max should be configurable.
|
||||
Lower the cost at probing state as we limit the size per message; lower the penalty when aggressively decreased to a too low `next`
|
||||
|
||||
2. limit the # of in flight messages < N when in `replicate` state. N should be configurable. Most implementation will have a sending buffer on top of its actual network transport layer (not blocking raft node). We want to make sure raft does not overflow that buffer, which can cause message dropping and triggering a bunch of unnecessary resending repeatedly.
|
||||
293
vendor/github.com/coreos/etcd/raft/doc.go
generated
vendored
Normal file
293
vendor/github.com/coreos/etcd/raft/doc.go
generated
vendored
Normal file
@@ -0,0 +1,293 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package raft sends and receives messages in the Protocol Buffer format
|
||||
defined in the raftpb package.
|
||||
|
||||
Raft is a protocol with which a cluster of nodes can maintain a replicated state machine.
|
||||
The state machine is kept in sync through the use of a replicated log.
|
||||
For more details on Raft, see "In Search of an Understandable Consensus Algorithm"
|
||||
(https://ramcloud.stanford.edu/raft.pdf) by Diego Ongaro and John Ousterhout.
|
||||
|
||||
A simple example application, _raftexample_, is also available to help illustrate
|
||||
how to use this package in practice:
|
||||
https://github.com/coreos/etcd/tree/master/contrib/raftexample
|
||||
|
||||
Usage
|
||||
|
||||
The primary object in raft is a Node. You either start a Node from scratch
|
||||
using raft.StartNode or start a Node from some initial state using raft.RestartNode.
|
||||
|
||||
To start a node from scratch:
|
||||
|
||||
storage := raft.NewMemoryStorage()
|
||||
c := &Config{
|
||||
ID: 0x01,
|
||||
ElectionTick: 10,
|
||||
HeartbeatTick: 1,
|
||||
Storage: storage,
|
||||
MaxSizePerMsg: 4096,
|
||||
MaxInflightMsgs: 256,
|
||||
}
|
||||
n := raft.StartNode(c, []raft.Peer{{ID: 0x02}, {ID: 0x03}})
|
||||
|
||||
To restart a node from previous state:
|
||||
|
||||
storage := raft.NewMemoryStorage()
|
||||
|
||||
// recover the in-memory storage from persistent
|
||||
// snapshot, state and entries.
|
||||
storage.ApplySnapshot(snapshot)
|
||||
storage.SetHardState(state)
|
||||
storage.Append(entries)
|
||||
|
||||
c := &Config{
|
||||
ID: 0x01,
|
||||
ElectionTick: 10,
|
||||
HeartbeatTick: 1,
|
||||
Storage: storage,
|
||||
MaxSizePerMsg: 4096,
|
||||
MaxInflightMsgs: 256,
|
||||
}
|
||||
|
||||
// restart raft without peer information.
|
||||
// peer information is already included in the storage.
|
||||
n := raft.RestartNode(c)
|
||||
|
||||
Now that you are holding onto a Node you have a few responsibilities:
|
||||
|
||||
First, you must read from the Node.Ready() channel and process the updates
|
||||
it contains. These steps may be performed in parallel, except as noted in step
|
||||
2.
|
||||
|
||||
1. Write HardState, Entries, and Snapshot to persistent storage if they are
|
||||
not empty. Note that when writing an Entry with Index i, any
|
||||
previously-persisted entries with Index >= i must be discarded.
|
||||
|
||||
2. Send all Messages to the nodes named in the To field. It is important that
|
||||
no messages be sent until after the latest HardState has been persisted to disk,
|
||||
and all Entries written by any previous Ready batch (Messages may be sent while
|
||||
entries from the same batch are being persisted). To reduce the I/O latency, an
|
||||
optimization can be applied to make leader write to disk in parallel with its
|
||||
followers (as explained at section 10.2.1 in Raft thesis). If any Message has type
|
||||
MsgSnap, call Node.ReportSnapshot() after it has been sent (these messages may be
|
||||
large).
|
||||
|
||||
Note: Marshalling messages is not thread-safe; it is important that you
|
||||
make sure that no new entries are persisted while marshalling.
|
||||
The easiest way to achieve this is to serialise the messages directly inside
|
||||
your main raft loop.
|
||||
|
||||
3. Apply Snapshot (if any) and CommittedEntries to the state machine.
|
||||
If any committed Entry has Type EntryConfChange, call Node.ApplyConfChange()
|
||||
to apply it to the node. The configuration change may be cancelled at this point
|
||||
by setting the NodeID field to zero before calling ApplyConfChange
|
||||
(but ApplyConfChange must be called one way or the other, and the decision to cancel
|
||||
must be based solely on the state machine and not external information such as
|
||||
the observed health of the node).
|
||||
|
||||
4. Call Node.Advance() to signal readiness for the next batch of updates.
|
||||
This may be done at any time after step 1, although all updates must be processed
|
||||
in the order they were returned by Ready.
|
||||
|
||||
Second, all persisted log entries must be made available via an
|
||||
implementation of the Storage interface. The provided MemoryStorage
|
||||
type can be used for this (if you repopulate its state upon a
|
||||
restart), or you can supply your own disk-backed implementation.
|
||||
|
||||
Third, when you receive a message from another node, pass it to Node.Step:
|
||||
|
||||
func recvRaftRPC(ctx context.Context, m raftpb.Message) {
|
||||
n.Step(ctx, m)
|
||||
}
|
||||
|
||||
Finally, you need to call Node.Tick() at regular intervals (probably
|
||||
via a time.Ticker). Raft has two important timeouts: heartbeat and the
|
||||
election timeout. However, internally to the raft package time is
|
||||
represented by an abstract "tick".
|
||||
|
||||
The total state machine handling loop will look something like this:
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.Ticker:
|
||||
n.Tick()
|
||||
case rd := <-s.Node.Ready():
|
||||
saveToStorage(rd.State, rd.Entries, rd.Snapshot)
|
||||
send(rd.Messages)
|
||||
if !raft.IsEmptySnap(rd.Snapshot) {
|
||||
processSnapshot(rd.Snapshot)
|
||||
}
|
||||
for _, entry := range rd.CommittedEntries {
|
||||
process(entry)
|
||||
if entry.Type == raftpb.EntryConfChange {
|
||||
var cc raftpb.ConfChange
|
||||
cc.Unmarshal(entry.Data)
|
||||
s.Node.ApplyConfChange(cc)
|
||||
}
|
||||
s.Node.Advance()
|
||||
case <-s.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
To propose changes to the state machine from your node take your application
|
||||
data, serialize it into a byte slice and call:
|
||||
|
||||
n.Propose(ctx, data)
|
||||
|
||||
If the proposal is committed, data will appear in committed entries with type
|
||||
raftpb.EntryNormal. There is no guarantee that a proposed command will be
|
||||
committed; you may have to re-propose after a timeout.
|
||||
|
||||
To add or remove node in a cluster, build ConfChange struct 'cc' and call:
|
||||
|
||||
n.ProposeConfChange(ctx, cc)
|
||||
|
||||
After config change is committed, some committed entry with type
|
||||
raftpb.EntryConfChange will be returned. You must apply it to node through:
|
||||
|
||||
var cc raftpb.ConfChange
|
||||
cc.Unmarshal(data)
|
||||
n.ApplyConfChange(cc)
|
||||
|
||||
Note: An ID represents a unique node in a cluster for all time. A
|
||||
given ID MUST be used only once even if the old node has been removed.
|
||||
This means that for example IP addresses make poor node IDs since they
|
||||
may be reused. Node IDs must be non-zero.
|
||||
|
||||
Implementation notes
|
||||
|
||||
This implementation is up to date with the final Raft thesis
|
||||
(https://ramcloud.stanford.edu/~ongaro/thesis.pdf), although our
|
||||
implementation of the membership change protocol differs somewhat from
|
||||
that described in chapter 4. The key invariant that membership changes
|
||||
happen one node at a time is preserved, but in our implementation the
|
||||
membership change takes effect when its entry is applied, not when it
|
||||
is added to the log (so the entry is committed under the old
|
||||
membership instead of the new). This is equivalent in terms of safety,
|
||||
since the old and new configurations are guaranteed to overlap.
|
||||
|
||||
To ensure that we do not attempt to commit two membership changes at
|
||||
once by matching log positions (which would be unsafe since they
|
||||
should have different quorum requirements), we simply disallow any
|
||||
proposed membership change while any uncommitted change appears in
|
||||
the leader's log.
|
||||
|
||||
This approach introduces a problem when you try to remove a member
|
||||
from a two-member cluster: If one of the members dies before the
|
||||
other one receives the commit of the confchange entry, then the member
|
||||
cannot be removed any more since the cluster cannot make progress.
|
||||
For this reason it is highly recommended to use three or more nodes in
|
||||
every cluster.
|
||||
|
||||
MessageType
|
||||
|
||||
Package raft sends and receives message in Protocol Buffer format (defined
|
||||
in raftpb package). Each state (follower, candidate, leader) implements its
|
||||
own 'step' method ('stepFollower', 'stepCandidate', 'stepLeader') when
|
||||
advancing with the given raftpb.Message. Each step is determined by its
|
||||
raftpb.MessageType. Note that every step is checked by one common method
|
||||
'Step' that safety-checks the terms of node and incoming message to prevent
|
||||
stale log entries:
|
||||
|
||||
'MsgHup' is used for election. If a node is a follower or candidate, the
|
||||
'tick' function in 'raft' struct is set as 'tickElection'. If a follower or
|
||||
candidate has not received any heartbeat before the election timeout, it
|
||||
passes 'MsgHup' to its Step method and becomes (or remains) a candidate to
|
||||
start a new election.
|
||||
|
||||
'MsgBeat' is an internal type that signals leaders to send a heartbeat of
|
||||
the 'MsgHeartbeat' type. If a node is a leader, the 'tick' function in
|
||||
the 'raft' struct is set as 'tickHeartbeat', and sends periodic heartbeat
|
||||
messages of the 'MsgBeat' type to its followers.
|
||||
|
||||
'MsgProp' proposes to append data to its log entries. This is a special
|
||||
type to redirect proposals to leader. Therefore, send method overwrites
|
||||
raftpb.Message's term with its HardState's term to avoid attaching its
|
||||
local term to 'MsgProp'. When 'MsgProp' is passed to the leader's 'Step'
|
||||
method, the leader first calls the 'appendEntry' method to append entries
|
||||
to its log, and then calls 'bcastAppend' method to send those entries to
|
||||
its peers. When passed to candidate, 'MsgProp' is dropped. When passed to
|
||||
follower, 'MsgProp' is stored in follower's mailbox(msgs) by the send
|
||||
method. It is stored with sender's ID and later forwarded to leader by
|
||||
rafthttp package.
|
||||
|
||||
'MsgApp' contains log entries to replicate. A leader calls bcastAppend,
|
||||
which calls sendAppend, which sends soon-to-be-replicated logs in 'MsgApp'
|
||||
type. When 'MsgApp' is passed to candidate's Step method, candidate reverts
|
||||
back to follower, because it indicates that there is a valid leader sending
|
||||
'MsgApp' messages. Candidate and follower respond to this message in
|
||||
'MsgAppResp' type.
|
||||
|
||||
'MsgAppResp' is response to log replication request('MsgApp'). When
|
||||
'MsgApp' is passed to candidate or follower's Step method, it responds by
|
||||
calling 'handleAppendEntries' method, which sends 'MsgAppResp' to raft
|
||||
mailbox.
|
||||
|
||||
'MsgVote' requests votes for election. When a node is a follower or
|
||||
candidate and 'MsgHup' is passed to its Step method, then the node calls
|
||||
'campaign' method to campaign itself to become a leader. Once 'campaign'
|
||||
method is called, the node becomes candidate and sends 'MsgVote' to peers
|
||||
in cluster to request votes. When passed to leader or candidate's Step
|
||||
method and the message's Term is lower than leader's or candidate's,
|
||||
'MsgVote' will be rejected ('MsgVoteResp' is returned with Reject true).
|
||||
If leader or candidate receives 'MsgVote' with higher term, it will revert
|
||||
back to follower. When 'MsgVote' is passed to follower, it votes for the
|
||||
sender only when sender's last term is greater than MsgVote's term or
|
||||
sender's last term is equal to MsgVote's term but sender's last committed
|
||||
index is greater than or equal to follower's.
|
||||
|
||||
'MsgVoteResp' contains responses from voting request. When 'MsgVoteResp' is
|
||||
passed to candidate, the candidate calculates how many votes it has won. If
|
||||
it's more than majority (quorum), it becomes leader and calls 'bcastAppend'.
|
||||
If candidate receives majority of votes of denials, it reverts back to
|
||||
follower.
|
||||
|
||||
'MsgSnap' requests to install a snapshot message. When a node has just
|
||||
become a leader or the leader receives 'MsgProp' message, it calls
|
||||
'bcastAppend' method, which then calls 'sendAppend' method to each
|
||||
follower. In 'sendAppend', if a leader fails to get term or entries,
|
||||
the leader requests snapshot by sending 'MsgSnap' type message.
|
||||
|
||||
'MsgSnapStatus' tells the result of snapshot install message. When a
|
||||
follower rejected 'MsgSnap', it indicates the snapshot request with
|
||||
'MsgSnap' had failed from network issues which causes the network layer
|
||||
to fail to send out snapshots to its followers. Then leader considers
|
||||
follower's progress as probe. When 'MsgSnap' were not rejected, it
|
||||
indicates that the snapshot succeeded and the leader sets follower's
|
||||
progress to probe and resumes its log replication.
|
||||
|
||||
'MsgHeartbeat' sends heartbeat from leader. When 'MsgHeartbeat' is passed
|
||||
to candidate and message's term is higher than candidate's, the candidate
|
||||
reverts back to follower and updates its committed index from the one in
|
||||
this heartbeat. And it sends the message to its mailbox. When
|
||||
'MsgHeartbeat' is passed to follower's Step method and message's term is
|
||||
higher than follower's, the follower updates its leaderID with the ID
|
||||
from the message.
|
||||
|
||||
'MsgHeartbeatResp' is a response to 'MsgHeartbeat'. When 'MsgHeartbeatResp'
|
||||
is passed to leader's Step method, the leader knows which follower
|
||||
responded. And only when the leader's last committed index is greater than
|
||||
follower's Match index, the leader runs 'sendAppend` method.
|
||||
|
||||
'MsgUnreachable' tells that request(message) wasn't delivered. When
|
||||
'MsgUnreachable' is passed to leader's Step method, the leader discovers
|
||||
that the follower that sent this 'MsgUnreachable' is not reachable, often
|
||||
indicating 'MsgApp' is lost. When follower's progress state is replicate,
|
||||
the leader sets it back to probe.
|
||||
|
||||
*/
|
||||
package raft
|
||||
358
vendor/github.com/coreos/etcd/raft/log.go
generated
vendored
Normal file
358
vendor/github.com/coreos/etcd/raft/log.go
generated
vendored
Normal file
@@ -0,0 +1,358 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
type raftLog struct {
|
||||
// storage contains all stable entries since the last snapshot.
|
||||
storage Storage
|
||||
|
||||
// unstable contains all unstable entries and snapshot.
|
||||
// they will be saved into storage.
|
||||
unstable unstable
|
||||
|
||||
// committed is the highest log position that is known to be in
|
||||
// stable storage on a quorum of nodes.
|
||||
committed uint64
|
||||
// applied is the highest log position that the application has
|
||||
// been instructed to apply to its state machine.
|
||||
// Invariant: applied <= committed
|
||||
applied uint64
|
||||
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// newLog returns log using the given storage. It recovers the log to the state
|
||||
// that it just commits and applies the latest snapshot.
|
||||
func newLog(storage Storage, logger Logger) *raftLog {
|
||||
if storage == nil {
|
||||
log.Panic("storage must not be nil")
|
||||
}
|
||||
log := &raftLog{
|
||||
storage: storage,
|
||||
logger: logger,
|
||||
}
|
||||
firstIndex, err := storage.FirstIndex()
|
||||
if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
lastIndex, err := storage.LastIndex()
|
||||
if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
log.unstable.offset = lastIndex + 1
|
||||
log.unstable.logger = logger
|
||||
// Initialize our committed and applied pointers to the time of the last compaction.
|
||||
log.committed = firstIndex - 1
|
||||
log.applied = firstIndex - 1
|
||||
|
||||
return log
|
||||
}
|
||||
|
||||
func (l *raftLog) String() string {
|
||||
return fmt.Sprintf("committed=%d, applied=%d, unstable.offset=%d, len(unstable.Entries)=%d", l.committed, l.applied, l.unstable.offset, len(l.unstable.entries))
|
||||
}
|
||||
|
||||
// maybeAppend returns (0, false) if the entries cannot be appended. Otherwise,
|
||||
// it returns (last index of new entries, true).
|
||||
func (l *raftLog) maybeAppend(index, logTerm, committed uint64, ents ...pb.Entry) (lastnewi uint64, ok bool) {
|
||||
lastnewi = index + uint64(len(ents))
|
||||
if l.matchTerm(index, logTerm) {
|
||||
ci := l.findConflict(ents)
|
||||
switch {
|
||||
case ci == 0:
|
||||
case ci <= l.committed:
|
||||
l.logger.Panicf("entry %d conflict with committed entry [committed(%d)]", ci, l.committed)
|
||||
default:
|
||||
offset := index + 1
|
||||
l.append(ents[ci-offset:]...)
|
||||
}
|
||||
l.commitTo(min(committed, lastnewi))
|
||||
return lastnewi, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (l *raftLog) append(ents ...pb.Entry) uint64 {
|
||||
if len(ents) == 0 {
|
||||
return l.lastIndex()
|
||||
}
|
||||
if after := ents[0].Index - 1; after < l.committed {
|
||||
l.logger.Panicf("after(%d) is out of range [committed(%d)]", after, l.committed)
|
||||
}
|
||||
l.unstable.truncateAndAppend(ents)
|
||||
return l.lastIndex()
|
||||
}
|
||||
|
||||
// findConflict finds the index of the conflict.
|
||||
// It returns the first pair of conflicting entries between the existing
|
||||
// entries and the given entries, if there are any.
|
||||
// If there is no conflicting entries, and the existing entries contains
|
||||
// all the given entries, zero will be returned.
|
||||
// If there is no conflicting entries, but the given entries contains new
|
||||
// entries, the index of the first new entry will be returned.
|
||||
// An entry is considered to be conflicting if it has the same index but
|
||||
// a different term.
|
||||
// The first entry MUST have an index equal to the argument 'from'.
|
||||
// The index of the given entries MUST be continuously increasing.
|
||||
func (l *raftLog) findConflict(ents []pb.Entry) uint64 {
|
||||
for _, ne := range ents {
|
||||
if !l.matchTerm(ne.Index, ne.Term) {
|
||||
if ne.Index <= l.lastIndex() {
|
||||
l.logger.Infof("found conflict at index %d [existing term: %d, conflicting term: %d]",
|
||||
ne.Index, l.zeroTermOnErrCompacted(l.term(ne.Index)), ne.Term)
|
||||
}
|
||||
return ne.Index
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (l *raftLog) unstableEntries() []pb.Entry {
|
||||
if len(l.unstable.entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
return l.unstable.entries
|
||||
}
|
||||
|
||||
// nextEnts returns all the available entries for execution.
|
||||
// If applied is smaller than the index of snapshot, it returns all committed
|
||||
// entries after the index of snapshot.
|
||||
func (l *raftLog) nextEnts() (ents []pb.Entry) {
|
||||
off := max(l.applied+1, l.firstIndex())
|
||||
if l.committed+1 > off {
|
||||
ents, err := l.slice(off, l.committed+1, noLimit)
|
||||
if err != nil {
|
||||
l.logger.Panicf("unexpected error when getting unapplied entries (%v)", err)
|
||||
}
|
||||
return ents
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasNextEnts returns if there is any available entries for execution. This
|
||||
// is a fast check without heavy raftLog.slice() in raftLog.nextEnts().
|
||||
func (l *raftLog) hasNextEnts() bool {
|
||||
off := max(l.applied+1, l.firstIndex())
|
||||
return l.committed+1 > off
|
||||
}
|
||||
|
||||
func (l *raftLog) snapshot() (pb.Snapshot, error) {
|
||||
if l.unstable.snapshot != nil {
|
||||
return *l.unstable.snapshot, nil
|
||||
}
|
||||
return l.storage.Snapshot()
|
||||
}
|
||||
|
||||
func (l *raftLog) firstIndex() uint64 {
|
||||
if i, ok := l.unstable.maybeFirstIndex(); ok {
|
||||
return i
|
||||
}
|
||||
index, err := l.storage.FirstIndex()
|
||||
if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func (l *raftLog) lastIndex() uint64 {
|
||||
if i, ok := l.unstable.maybeLastIndex(); ok {
|
||||
return i
|
||||
}
|
||||
i, err := l.storage.LastIndex()
|
||||
if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (l *raftLog) commitTo(tocommit uint64) {
|
||||
// never decrease commit
|
||||
if l.committed < tocommit {
|
||||
if l.lastIndex() < tocommit {
|
||||
l.logger.Panicf("tocommit(%d) is out of range [lastIndex(%d)]. Was the raft log corrupted, truncated, or lost?", tocommit, l.lastIndex())
|
||||
}
|
||||
l.committed = tocommit
|
||||
}
|
||||
}
|
||||
|
||||
func (l *raftLog) appliedTo(i uint64) {
|
||||
if i == 0 {
|
||||
return
|
||||
}
|
||||
if l.committed < i || i < l.applied {
|
||||
l.logger.Panicf("applied(%d) is out of range [prevApplied(%d), committed(%d)]", i, l.applied, l.committed)
|
||||
}
|
||||
l.applied = i
|
||||
}
|
||||
|
||||
func (l *raftLog) stableTo(i, t uint64) { l.unstable.stableTo(i, t) }
|
||||
|
||||
func (l *raftLog) stableSnapTo(i uint64) { l.unstable.stableSnapTo(i) }
|
||||
|
||||
func (l *raftLog) lastTerm() uint64 {
|
||||
t, err := l.term(l.lastIndex())
|
||||
if err != nil {
|
||||
l.logger.Panicf("unexpected error when getting the last term (%v)", err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (l *raftLog) term(i uint64) (uint64, error) {
|
||||
// the valid term range is [index of dummy entry, last index]
|
||||
dummyIndex := l.firstIndex() - 1
|
||||
if i < dummyIndex || i > l.lastIndex() {
|
||||
// TODO: return an error instead?
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if t, ok := l.unstable.maybeTerm(i); ok {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
t, err := l.storage.Term(i)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
if err == ErrCompacted {
|
||||
return 0, err
|
||||
}
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
|
||||
func (l *raftLog) entries(i, maxsize uint64) ([]pb.Entry, error) {
|
||||
if i > l.lastIndex() {
|
||||
return nil, nil
|
||||
}
|
||||
return l.slice(i, l.lastIndex()+1, maxsize)
|
||||
}
|
||||
|
||||
// allEntries returns all entries in the log.
|
||||
func (l *raftLog) allEntries() []pb.Entry {
|
||||
ents, err := l.entries(l.firstIndex(), noLimit)
|
||||
if err == nil {
|
||||
return ents
|
||||
}
|
||||
if err == ErrCompacted { // try again if there was a racing compaction
|
||||
return l.allEntries()
|
||||
}
|
||||
// TODO (xiangli): handle error?
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// isUpToDate determines if the given (lastIndex,term) log is more up-to-date
|
||||
// by comparing the index and term of the last entries in the existing logs.
|
||||
// If the logs have last entries with different terms, then the log with the
|
||||
// later term is more up-to-date. If the logs end with the same term, then
|
||||
// whichever log has the larger lastIndex is more up-to-date. If the logs are
|
||||
// the same, the given log is up-to-date.
|
||||
func (l *raftLog) isUpToDate(lasti, term uint64) bool {
|
||||
return term > l.lastTerm() || (term == l.lastTerm() && lasti >= l.lastIndex())
|
||||
}
|
||||
|
||||
func (l *raftLog) matchTerm(i, term uint64) bool {
|
||||
t, err := l.term(i)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return t == term
|
||||
}
|
||||
|
||||
func (l *raftLog) maybeCommit(maxIndex, term uint64) bool {
|
||||
if maxIndex > l.committed && l.zeroTermOnErrCompacted(l.term(maxIndex)) == term {
|
||||
l.commitTo(maxIndex)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *raftLog) restore(s pb.Snapshot) {
|
||||
l.logger.Infof("log [%s] starts to restore snapshot [index: %d, term: %d]", l, s.Metadata.Index, s.Metadata.Term)
|
||||
l.committed = s.Metadata.Index
|
||||
l.unstable.restore(s)
|
||||
}
|
||||
|
||||
// slice returns a slice of log entries from lo through hi-1, inclusive.
|
||||
func (l *raftLog) slice(lo, hi, maxSize uint64) ([]pb.Entry, error) {
|
||||
err := l.mustCheckOutOfBounds(lo, hi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lo == hi {
|
||||
return nil, nil
|
||||
}
|
||||
var ents []pb.Entry
|
||||
if lo < l.unstable.offset {
|
||||
storedEnts, err := l.storage.Entries(lo, min(hi, l.unstable.offset), maxSize)
|
||||
if err == ErrCompacted {
|
||||
return nil, err
|
||||
} else if err == ErrUnavailable {
|
||||
l.logger.Panicf("entries[%d:%d) is unavailable from storage", lo, min(hi, l.unstable.offset))
|
||||
} else if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
|
||||
// check if ents has reached the size limitation
|
||||
if uint64(len(storedEnts)) < min(hi, l.unstable.offset)-lo {
|
||||
return storedEnts, nil
|
||||
}
|
||||
|
||||
ents = storedEnts
|
||||
}
|
||||
if hi > l.unstable.offset {
|
||||
unstable := l.unstable.slice(max(lo, l.unstable.offset), hi)
|
||||
if len(ents) > 0 {
|
||||
ents = append([]pb.Entry{}, ents...)
|
||||
ents = append(ents, unstable...)
|
||||
} else {
|
||||
ents = unstable
|
||||
}
|
||||
}
|
||||
return limitSize(ents, maxSize), nil
|
||||
}
|
||||
|
||||
// l.firstIndex <= lo <= hi <= l.firstIndex + len(l.entries)
|
||||
func (l *raftLog) mustCheckOutOfBounds(lo, hi uint64) error {
|
||||
if lo > hi {
|
||||
l.logger.Panicf("invalid slice %d > %d", lo, hi)
|
||||
}
|
||||
fi := l.firstIndex()
|
||||
if lo < fi {
|
||||
return ErrCompacted
|
||||
}
|
||||
|
||||
length := l.lastIndex() - fi + 1
|
||||
if lo < fi || hi > fi+length {
|
||||
l.logger.Panicf("slice[%d,%d) out of bound [%d,%d]", lo, hi, fi, l.lastIndex())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *raftLog) zeroTermOnErrCompacted(t uint64, err error) uint64 {
|
||||
if err == nil {
|
||||
return t
|
||||
}
|
||||
if err == ErrCompacted {
|
||||
return 0
|
||||
}
|
||||
l.logger.Panicf("unexpected error (%v)", err)
|
||||
return 0
|
||||
}
|
||||
139
vendor/github.com/coreos/etcd/raft/log_unstable.go
generated
vendored
Normal file
139
vendor/github.com/coreos/etcd/raft/log_unstable.go
generated
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raft
|
||||
|
||||
import pb "github.com/coreos/etcd/raft/raftpb"
|
||||
|
||||
// unstable.entries[i] has raft log position i+unstable.offset.
|
||||
// Note that unstable.offset may be less than the highest log
|
||||
// position in storage; this means that the next write to storage
|
||||
// might need to truncate the log before persisting unstable.entries.
|
||||
type unstable struct {
|
||||
// the incoming unstable snapshot, if any.
|
||||
snapshot *pb.Snapshot
|
||||
// all entries that have not yet been written to storage.
|
||||
entries []pb.Entry
|
||||
offset uint64
|
||||
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// maybeFirstIndex returns the index of the first possible entry in entries
|
||||
// if it has a snapshot.
|
||||
func (u *unstable) maybeFirstIndex() (uint64, bool) {
|
||||
if u.snapshot != nil {
|
||||
return u.snapshot.Metadata.Index + 1, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// maybeLastIndex returns the last index if it has at least one
|
||||
// unstable entry or snapshot.
|
||||
func (u *unstable) maybeLastIndex() (uint64, bool) {
|
||||
if l := len(u.entries); l != 0 {
|
||||
return u.offset + uint64(l) - 1, true
|
||||
}
|
||||
if u.snapshot != nil {
|
||||
return u.snapshot.Metadata.Index, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// maybeTerm returns the term of the entry at index i, if there
|
||||
// is any.
|
||||
func (u *unstable) maybeTerm(i uint64) (uint64, bool) {
|
||||
if i < u.offset {
|
||||
if u.snapshot == nil {
|
||||
return 0, false
|
||||
}
|
||||
if u.snapshot.Metadata.Index == i {
|
||||
return u.snapshot.Metadata.Term, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
last, ok := u.maybeLastIndex()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
if i > last {
|
||||
return 0, false
|
||||
}
|
||||
return u.entries[i-u.offset].Term, true
|
||||
}
|
||||
|
||||
func (u *unstable) stableTo(i, t uint64) {
|
||||
gt, ok := u.maybeTerm(i)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// if i < offset, term is matched with the snapshot
|
||||
// only update the unstable entries if term is matched with
|
||||
// an unstable entry.
|
||||
if gt == t && i >= u.offset {
|
||||
u.entries = u.entries[i+1-u.offset:]
|
||||
u.offset = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
func (u *unstable) stableSnapTo(i uint64) {
|
||||
if u.snapshot != nil && u.snapshot.Metadata.Index == i {
|
||||
u.snapshot = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *unstable) restore(s pb.Snapshot) {
|
||||
u.offset = s.Metadata.Index + 1
|
||||
u.entries = nil
|
||||
u.snapshot = &s
|
||||
}
|
||||
|
||||
func (u *unstable) truncateAndAppend(ents []pb.Entry) {
|
||||
after := ents[0].Index - 1
|
||||
switch {
|
||||
case after == u.offset+uint64(len(u.entries))-1:
|
||||
// after is the last index in the u.entries
|
||||
// directly append
|
||||
u.entries = append(u.entries, ents...)
|
||||
case after < u.offset:
|
||||
u.logger.Infof("replace the unstable entries from index %d", after+1)
|
||||
// The log is being truncated to before our current offset
|
||||
// portion, so set the offset and replace the entries
|
||||
u.offset = after + 1
|
||||
u.entries = ents
|
||||
default:
|
||||
// truncate to after and copy to u.entries
|
||||
// then append
|
||||
u.logger.Infof("truncate the unstable entries to index %d", after)
|
||||
u.entries = append([]pb.Entry{}, u.slice(u.offset, after+1)...)
|
||||
u.entries = append(u.entries, ents...)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *unstable) slice(lo uint64, hi uint64) []pb.Entry {
|
||||
u.mustCheckOutOfBounds(lo, hi)
|
||||
return u.entries[lo-u.offset : hi-u.offset]
|
||||
}
|
||||
|
||||
// u.offset <= lo <= hi <= u.offset+len(u.offset)
|
||||
func (u *unstable) mustCheckOutOfBounds(lo, hi uint64) {
|
||||
if lo > hi {
|
||||
u.logger.Panicf("invalid unstable.slice %d > %d", lo, hi)
|
||||
}
|
||||
upper := u.offset + uint64(len(u.entries))
|
||||
if lo < u.offset || hi > upper {
|
||||
u.logger.Panicf("unstable.slice[%d,%d) out of bound [%d,%d]", lo, hi, u.offset, upper)
|
||||
}
|
||||
}
|
||||
126
vendor/github.com/coreos/etcd/raft/logger.go
generated
vendored
Normal file
126
vendor/github.com/coreos/etcd/raft/logger.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Debug(v ...interface{})
|
||||
Debugf(format string, v ...interface{})
|
||||
|
||||
Error(v ...interface{})
|
||||
Errorf(format string, v ...interface{})
|
||||
|
||||
Info(v ...interface{})
|
||||
Infof(format string, v ...interface{})
|
||||
|
||||
Warning(v ...interface{})
|
||||
Warningf(format string, v ...interface{})
|
||||
|
||||
Fatal(v ...interface{})
|
||||
Fatalf(format string, v ...interface{})
|
||||
|
||||
Panic(v ...interface{})
|
||||
Panicf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
func SetLogger(l Logger) { raftLogger = l }
|
||||
|
||||
var (
|
||||
defaultLogger = &DefaultLogger{Logger: log.New(os.Stderr, "raft", log.LstdFlags)}
|
||||
discardLogger = &DefaultLogger{Logger: log.New(ioutil.Discard, "", 0)}
|
||||
raftLogger = Logger(defaultLogger)
|
||||
)
|
||||
|
||||
const (
|
||||
calldepth = 2
|
||||
)
|
||||
|
||||
// DefaultLogger is a default implementation of the Logger interface.
|
||||
type DefaultLogger struct {
|
||||
*log.Logger
|
||||
debug bool
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) EnableTimestamps() {
|
||||
l.SetFlags(l.Flags() | log.Ldate | log.Ltime)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) EnableDebug() {
|
||||
l.debug = true
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Debug(v ...interface{}) {
|
||||
if l.debug {
|
||||
l.Output(calldepth, header("DEBUG", fmt.Sprint(v...)))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Debugf(format string, v ...interface{}) {
|
||||
if l.debug {
|
||||
l.Output(calldepth, header("DEBUG", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Info(v ...interface{}) {
|
||||
l.Output(calldepth, header("INFO", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Infof(format string, v ...interface{}) {
|
||||
l.Output(calldepth, header("INFO", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Error(v ...interface{}) {
|
||||
l.Output(calldepth, header("ERROR", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Errorf(format string, v ...interface{}) {
|
||||
l.Output(calldepth, header("ERROR", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Warning(v ...interface{}) {
|
||||
l.Output(calldepth, header("WARN", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Warningf(format string, v ...interface{}) {
|
||||
l.Output(calldepth, header("WARN", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Fatal(v ...interface{}) {
|
||||
l.Output(calldepth, header("FATAL", fmt.Sprint(v...)))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Fatalf(format string, v ...interface{}) {
|
||||
l.Output(calldepth, header("FATAL", fmt.Sprintf(format, v...)))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Panic(v ...interface{}) {
|
||||
l.Logger.Panic(v)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Panicf(format string, v ...interface{}) {
|
||||
l.Logger.Panicf(format, v...)
|
||||
}
|
||||
|
||||
func header(lvl, msg string) string {
|
||||
return fmt.Sprintf("%s: %s", lvl, msg)
|
||||
}
|
||||
488
vendor/github.com/coreos/etcd/raft/node.go
generated
vendored
Normal file
488
vendor/github.com/coreos/etcd/raft/node.go
generated
vendored
Normal file
@@ -0,0 +1,488 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raft
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type SnapshotStatus int
|
||||
|
||||
const (
|
||||
SnapshotFinish SnapshotStatus = 1
|
||||
SnapshotFailure SnapshotStatus = 2
|
||||
)
|
||||
|
||||
var (
|
||||
emptyState = pb.HardState{}
|
||||
|
||||
// ErrStopped is returned by methods on Nodes that have been stopped.
|
||||
ErrStopped = errors.New("raft: stopped")
|
||||
)
|
||||
|
||||
// SoftState provides state that is useful for logging and debugging.
|
||||
// The state is volatile and does not need to be persisted to the WAL.
|
||||
type SoftState struct {
|
||||
Lead uint64
|
||||
RaftState StateType
|
||||
}
|
||||
|
||||
func (a *SoftState) equal(b *SoftState) bool {
|
||||
return a.Lead == b.Lead && a.RaftState == b.RaftState
|
||||
}
|
||||
|
||||
// Ready encapsulates the entries and messages that are ready to read,
|
||||
// be saved to stable storage, committed or sent to other peers.
|
||||
// All fields in Ready are read-only.
|
||||
type Ready struct {
|
||||
// The current volatile state of a Node.
|
||||
// SoftState will be nil if there is no update.
|
||||
// It is not required to consume or store SoftState.
|
||||
*SoftState
|
||||
|
||||
// The current state of a Node to be saved to stable storage BEFORE
|
||||
// Messages are sent.
|
||||
// HardState will be equal to empty state if there is no update.
|
||||
pb.HardState
|
||||
|
||||
// Entries specifies entries to be saved to stable storage BEFORE
|
||||
// Messages are sent.
|
||||
Entries []pb.Entry
|
||||
|
||||
// Snapshot specifies the snapshot to be saved to stable storage.
|
||||
Snapshot pb.Snapshot
|
||||
|
||||
// CommittedEntries specifies entries to be committed to a
|
||||
// store/state-machine. These have previously been committed to stable
|
||||
// store.
|
||||
CommittedEntries []pb.Entry
|
||||
|
||||
// Messages specifies outbound messages to be sent AFTER Entries are
|
||||
// committed to stable storage.
|
||||
// If it contains a MsgSnap message, the application MUST report back to raft
|
||||
// when the snapshot has been received or has failed by calling ReportSnapshot.
|
||||
Messages []pb.Message
|
||||
}
|
||||
|
||||
func isHardStateEqual(a, b pb.HardState) bool {
|
||||
return a.Term == b.Term && a.Vote == b.Vote && a.Commit == b.Commit
|
||||
}
|
||||
|
||||
// IsEmptyHardState returns true if the given HardState is empty.
|
||||
func IsEmptyHardState(st pb.HardState) bool {
|
||||
return isHardStateEqual(st, emptyState)
|
||||
}
|
||||
|
||||
// IsEmptySnap returns true if the given Snapshot is empty.
|
||||
func IsEmptySnap(sp pb.Snapshot) bool {
|
||||
return sp.Metadata.Index == 0
|
||||
}
|
||||
|
||||
func (rd Ready) containsUpdates() bool {
|
||||
return rd.SoftState != nil || !IsEmptyHardState(rd.HardState) ||
|
||||
!IsEmptySnap(rd.Snapshot) || len(rd.Entries) > 0 ||
|
||||
len(rd.CommittedEntries) > 0 || len(rd.Messages) > 0
|
||||
}
|
||||
|
||||
// Node represents a node in a raft cluster.
|
||||
type Node interface {
|
||||
// Tick increments the internal logical clock for the Node by a single tick. Election
|
||||
// timeouts and heartbeat timeouts are in units of ticks.
|
||||
Tick()
|
||||
// Campaign causes the Node to transition to candidate state and start campaigning to become leader.
|
||||
Campaign(ctx context.Context) error
|
||||
// Propose proposes that data be appended to the log.
|
||||
Propose(ctx context.Context, data []byte) error
|
||||
// ProposeConfChange proposes config change.
|
||||
// At most one ConfChange can be in the process of going through consensus.
|
||||
// Application needs to call ApplyConfChange when applying EntryConfChange type entry.
|
||||
ProposeConfChange(ctx context.Context, cc pb.ConfChange) error
|
||||
// Step advances the state machine using the given message. ctx.Err() will be returned, if any.
|
||||
Step(ctx context.Context, msg pb.Message) error
|
||||
|
||||
// Ready returns a channel that returns the current point-in-time state.
|
||||
// Users of the Node must call Advance after retrieving the state returned by Ready.
|
||||
//
|
||||
// NOTE: No committed entries from the next Ready may be applied until all committed entries
|
||||
// and snapshots from the previous one have finished.
|
||||
Ready() <-chan Ready
|
||||
|
||||
// Advance notifies the Node that the application has saved progress up to the last Ready.
|
||||
// It prepares the node to return the next available Ready.
|
||||
//
|
||||
// The application should generally call Advance after it applies the entries in last Ready.
|
||||
//
|
||||
// However, as an optimization, the application may call Advance while it is applying the
|
||||
// commands. For example. when the last Ready contains a snapshot, the application might take
|
||||
// a long time to apply the snapshot data. To continue receiving Ready without blocking raft
|
||||
// progress, it can call Advance before finish applying the last ready. To make this optimization
|
||||
// work safely, when the application receives a Ready with softState.RaftState equal to Candidate
|
||||
// it MUST apply all pending configuration changes if there is any.
|
||||
//
|
||||
// Here is a simple solution that waiting for ALL pending entries to get applied.
|
||||
// ```
|
||||
// ...
|
||||
// rd := <-n.Ready()
|
||||
// go apply(rd.CommittedEntries) // optimization to apply asynchronously in FIFO order.
|
||||
// if rd.SoftState.RaftState == StateCandidate {
|
||||
// waitAllApplied()
|
||||
// }
|
||||
// n.Advance()
|
||||
// ...
|
||||
//```
|
||||
Advance()
|
||||
// ApplyConfChange applies config change to the local node.
|
||||
// Returns an opaque ConfState protobuf which must be recorded
|
||||
// in snapshots. Will never return nil; it returns a pointer only
|
||||
// to match MemoryStorage.Compact.
|
||||
ApplyConfChange(cc pb.ConfChange) *pb.ConfState
|
||||
// Status returns the current status of the raft state machine.
|
||||
Status() Status
|
||||
// ReportUnreachable reports the given node is not reachable for the last send.
|
||||
ReportUnreachable(id uint64)
|
||||
// ReportSnapshot reports the status of the sent snapshot.
|
||||
ReportSnapshot(id uint64, status SnapshotStatus)
|
||||
// Stop performs any necessary termination of the Node.
|
||||
Stop()
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
ID uint64
|
||||
Context []byte
|
||||
}
|
||||
|
||||
// StartNode returns a new Node given configuration and a list of raft peers.
|
||||
// It appends a ConfChangeAddNode entry for each given peer to the initial log.
|
||||
func StartNode(c *Config, peers []Peer) Node {
|
||||
r := newRaft(c)
|
||||
// become the follower at term 1 and apply initial configuration
|
||||
// entries of term 1
|
||||
r.becomeFollower(1, None)
|
||||
for _, peer := range peers {
|
||||
cc := pb.ConfChange{Type: pb.ConfChangeAddNode, NodeID: peer.ID, Context: peer.Context}
|
||||
d, err := cc.Marshal()
|
||||
if err != nil {
|
||||
panic("unexpected marshal error")
|
||||
}
|
||||
e := pb.Entry{Type: pb.EntryConfChange, Term: 1, Index: r.raftLog.lastIndex() + 1, Data: d}
|
||||
r.raftLog.append(e)
|
||||
}
|
||||
// Mark these initial entries as committed.
|
||||
// TODO(bdarnell): These entries are still unstable; do we need to preserve
|
||||
// the invariant that committed < unstable?
|
||||
r.raftLog.committed = r.raftLog.lastIndex()
|
||||
// Now apply them, mainly so that the application can call Campaign
|
||||
// immediately after StartNode in tests. Note that these nodes will
|
||||
// be added to raft twice: here and when the application's Ready
|
||||
// loop calls ApplyConfChange. The calls to addNode must come after
|
||||
// all calls to raftLog.append so progress.next is set after these
|
||||
// bootstrapping entries (it is an error if we try to append these
|
||||
// entries since they have already been committed).
|
||||
// We do not set raftLog.applied so the application will be able
|
||||
// to observe all conf changes via Ready.CommittedEntries.
|
||||
for _, peer := range peers {
|
||||
r.addNode(peer.ID)
|
||||
}
|
||||
|
||||
n := newNode()
|
||||
go n.run(r)
|
||||
return &n
|
||||
}
|
||||
|
||||
// RestartNode is similar to StartNode but does not take a list of peers.
|
||||
// The current membership of the cluster will be restored from the Storage.
|
||||
// If the caller has an existing state machine, pass in the last log index that
|
||||
// has been applied to it; otherwise use zero.
|
||||
func RestartNode(c *Config) Node {
|
||||
r := newRaft(c)
|
||||
|
||||
n := newNode()
|
||||
go n.run(r)
|
||||
return &n
|
||||
}
|
||||
|
||||
// node is the canonical implementation of the Node interface
|
||||
type node struct {
|
||||
propc chan pb.Message
|
||||
recvc chan pb.Message
|
||||
confc chan pb.ConfChange
|
||||
confstatec chan pb.ConfState
|
||||
readyc chan Ready
|
||||
advancec chan struct{}
|
||||
tickc chan struct{}
|
||||
done chan struct{}
|
||||
stop chan struct{}
|
||||
status chan chan Status
|
||||
}
|
||||
|
||||
func newNode() node {
|
||||
return node{
|
||||
propc: make(chan pb.Message),
|
||||
recvc: make(chan pb.Message),
|
||||
confc: make(chan pb.ConfChange),
|
||||
confstatec: make(chan pb.ConfState),
|
||||
readyc: make(chan Ready),
|
||||
advancec: make(chan struct{}),
|
||||
tickc: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
stop: make(chan struct{}),
|
||||
status: make(chan chan Status),
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) Stop() {
|
||||
select {
|
||||
case n.stop <- struct{}{}:
|
||||
// Not already stopped, so trigger it
|
||||
case <-n.done:
|
||||
// Node has already been stopped - no need to do anything
|
||||
return
|
||||
}
|
||||
// Block until the stop has been acknowledged by run()
|
||||
<-n.done
|
||||
}
|
||||
|
||||
func (n *node) run(r *raft) {
|
||||
var propc chan pb.Message
|
||||
var readyc chan Ready
|
||||
var advancec chan struct{}
|
||||
var prevLastUnstablei, prevLastUnstablet uint64
|
||||
var havePrevLastUnstablei bool
|
||||
var prevSnapi uint64
|
||||
var rd Ready
|
||||
|
||||
lead := None
|
||||
prevSoftSt := r.softState()
|
||||
prevHardSt := emptyState
|
||||
|
||||
for {
|
||||
if advancec != nil {
|
||||
readyc = nil
|
||||
} else {
|
||||
rd = newReady(r, prevSoftSt, prevHardSt)
|
||||
if rd.containsUpdates() {
|
||||
readyc = n.readyc
|
||||
} else {
|
||||
readyc = nil
|
||||
}
|
||||
}
|
||||
|
||||
if lead != r.lead {
|
||||
if r.hasLeader() {
|
||||
if lead == None {
|
||||
r.logger.Infof("raft.node: %x elected leader %x at term %d", r.id, r.lead, r.Term)
|
||||
} else {
|
||||
r.logger.Infof("raft.node: %x changed leader from %x to %x at term %d", r.id, lead, r.lead, r.Term)
|
||||
}
|
||||
propc = n.propc
|
||||
} else {
|
||||
r.logger.Infof("raft.node: %x lost leader %x at term %d", r.id, lead, r.Term)
|
||||
propc = nil
|
||||
}
|
||||
lead = r.lead
|
||||
}
|
||||
|
||||
select {
|
||||
// TODO: maybe buffer the config propose if there exists one (the way
|
||||
// described in raft dissertation)
|
||||
// Currently it is dropped in Step silently.
|
||||
case m := <-propc:
|
||||
m.From = r.id
|
||||
r.Step(m)
|
||||
case m := <-n.recvc:
|
||||
// filter out response message from unknown From.
|
||||
if _, ok := r.prs[m.From]; ok || !IsResponseMsg(m) {
|
||||
r.Step(m) // raft never returns an error
|
||||
}
|
||||
case cc := <-n.confc:
|
||||
if cc.NodeID == None {
|
||||
r.resetPendingConf()
|
||||
select {
|
||||
case n.confstatec <- pb.ConfState{Nodes: r.nodes()}:
|
||||
case <-n.done:
|
||||
}
|
||||
break
|
||||
}
|
||||
switch cc.Type {
|
||||
case pb.ConfChangeAddNode:
|
||||
r.addNode(cc.NodeID)
|
||||
case pb.ConfChangeRemoveNode:
|
||||
// block incoming proposal when local node is
|
||||
// removed
|
||||
if cc.NodeID == r.id {
|
||||
n.propc = nil
|
||||
}
|
||||
r.removeNode(cc.NodeID)
|
||||
case pb.ConfChangeUpdateNode:
|
||||
r.resetPendingConf()
|
||||
default:
|
||||
panic("unexpected conf type")
|
||||
}
|
||||
select {
|
||||
case n.confstatec <- pb.ConfState{Nodes: r.nodes()}:
|
||||
case <-n.done:
|
||||
}
|
||||
case <-n.tickc:
|
||||
r.tick()
|
||||
case readyc <- rd:
|
||||
if rd.SoftState != nil {
|
||||
prevSoftSt = rd.SoftState
|
||||
}
|
||||
if len(rd.Entries) > 0 {
|
||||
prevLastUnstablei = rd.Entries[len(rd.Entries)-1].Index
|
||||
prevLastUnstablet = rd.Entries[len(rd.Entries)-1].Term
|
||||
havePrevLastUnstablei = true
|
||||
}
|
||||
if !IsEmptyHardState(rd.HardState) {
|
||||
prevHardSt = rd.HardState
|
||||
}
|
||||
if !IsEmptySnap(rd.Snapshot) {
|
||||
prevSnapi = rd.Snapshot.Metadata.Index
|
||||
}
|
||||
r.msgs = nil
|
||||
advancec = n.advancec
|
||||
case <-advancec:
|
||||
if prevHardSt.Commit != 0 {
|
||||
r.raftLog.appliedTo(prevHardSt.Commit)
|
||||
}
|
||||
if havePrevLastUnstablei {
|
||||
r.raftLog.stableTo(prevLastUnstablei, prevLastUnstablet)
|
||||
havePrevLastUnstablei = false
|
||||
}
|
||||
r.raftLog.stableSnapTo(prevSnapi)
|
||||
advancec = nil
|
||||
case c := <-n.status:
|
||||
c <- getStatus(r)
|
||||
case <-n.stop:
|
||||
close(n.done)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tick increments the internal logical clock for this Node. Election timeouts
|
||||
// and heartbeat timeouts are in units of ticks.
|
||||
func (n *node) Tick() {
|
||||
select {
|
||||
case n.tickc <- struct{}{}:
|
||||
case <-n.done:
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) Campaign(ctx context.Context) error { return n.step(ctx, pb.Message{Type: pb.MsgHup}) }
|
||||
|
||||
func (n *node) Propose(ctx context.Context, data []byte) error {
|
||||
return n.step(ctx, pb.Message{Type: pb.MsgProp, Entries: []pb.Entry{{Data: data}}})
|
||||
}
|
||||
|
||||
func (n *node) Step(ctx context.Context, m pb.Message) error {
|
||||
// ignore unexpected local messages receiving over network
|
||||
if IsLocalMsg(m) {
|
||||
// TODO: return an error?
|
||||
return nil
|
||||
}
|
||||
return n.step(ctx, m)
|
||||
}
|
||||
|
||||
func (n *node) ProposeConfChange(ctx context.Context, cc pb.ConfChange) error {
|
||||
data, err := cc.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return n.Step(ctx, pb.Message{Type: pb.MsgProp, Entries: []pb.Entry{{Type: pb.EntryConfChange, Data: data}}})
|
||||
}
|
||||
|
||||
// Step advances the state machine using msgs. The ctx.Err() will be returned,
|
||||
// if any.
|
||||
func (n *node) step(ctx context.Context, m pb.Message) error {
|
||||
ch := n.recvc
|
||||
if m.Type == pb.MsgProp {
|
||||
ch = n.propc
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- m:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-n.done:
|
||||
return ErrStopped
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) Ready() <-chan Ready { return n.readyc }
|
||||
|
||||
func (n *node) Advance() {
|
||||
select {
|
||||
case n.advancec <- struct{}{}:
|
||||
case <-n.done:
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) ApplyConfChange(cc pb.ConfChange) *pb.ConfState {
|
||||
var cs pb.ConfState
|
||||
select {
|
||||
case n.confc <- cc:
|
||||
case <-n.done:
|
||||
}
|
||||
select {
|
||||
case cs = <-n.confstatec:
|
||||
case <-n.done:
|
||||
}
|
||||
return &cs
|
||||
}
|
||||
|
||||
func (n *node) Status() Status {
|
||||
c := make(chan Status)
|
||||
n.status <- c
|
||||
return <-c
|
||||
}
|
||||
|
||||
func (n *node) ReportUnreachable(id uint64) {
|
||||
select {
|
||||
case n.recvc <- pb.Message{Type: pb.MsgUnreachable, From: id}:
|
||||
case <-n.done:
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) ReportSnapshot(id uint64, status SnapshotStatus) {
|
||||
rej := status == SnapshotFailure
|
||||
|
||||
select {
|
||||
case n.recvc <- pb.Message{Type: pb.MsgSnapStatus, From: id, Reject: rej}:
|
||||
case <-n.done:
|
||||
}
|
||||
}
|
||||
|
||||
func newReady(r *raft, prevSoftSt *SoftState, prevHardSt pb.HardState) Ready {
|
||||
rd := Ready{
|
||||
Entries: r.raftLog.unstableEntries(),
|
||||
CommittedEntries: r.raftLog.nextEnts(),
|
||||
Messages: r.msgs,
|
||||
}
|
||||
if softSt := r.softState(); !softSt.equal(prevSoftSt) {
|
||||
rd.SoftState = softSt
|
||||
}
|
||||
if hardSt := r.hardState(); !isHardStateEqual(hardSt, prevHardSt) {
|
||||
rd.HardState = hardSt
|
||||
}
|
||||
if r.raftLog.unstable.snapshot != nil {
|
||||
rd.Snapshot = *r.raftLog.unstable.snapshot
|
||||
}
|
||||
return rd
|
||||
}
|
||||
245
vendor/github.com/coreos/etcd/raft/progress.go
generated
vendored
Normal file
245
vendor/github.com/coreos/etcd/raft/progress.go
generated
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raft
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
ProgressStateProbe ProgressStateType = iota
|
||||
ProgressStateReplicate
|
||||
ProgressStateSnapshot
|
||||
)
|
||||
|
||||
type ProgressStateType uint64
|
||||
|
||||
var prstmap = [...]string{
|
||||
"ProgressStateProbe",
|
||||
"ProgressStateReplicate",
|
||||
"ProgressStateSnapshot",
|
||||
}
|
||||
|
||||
func (st ProgressStateType) String() string { return prstmap[uint64(st)] }
|
||||
|
||||
// Progress represents a follower’s progress in the view of the leader. Leader maintains
|
||||
// progresses of all followers, and sends entries to the follower based on its progress.
|
||||
type Progress struct {
|
||||
Match, Next uint64
|
||||
// State defines how the leader should interact with the follower.
|
||||
//
|
||||
// When in ProgressStateProbe, leader sends at most one replication message
|
||||
// per heartbeat interval. It also probes actual progress of the follower.
|
||||
//
|
||||
// When in ProgressStateReplicate, leader optimistically increases next
|
||||
// to the latest entry sent after sending replication message. This is
|
||||
// an optimized state for fast replicating log entries to the follower.
|
||||
//
|
||||
// When in ProgressStateSnapshot, leader should have sent out snapshot
|
||||
// before and stops sending any replication message.
|
||||
State ProgressStateType
|
||||
// Paused is used in ProgressStateProbe.
|
||||
// When Paused is true, raft should pause sending replication message to this peer.
|
||||
Paused bool
|
||||
// PendingSnapshot is used in ProgressStateSnapshot.
|
||||
// If there is a pending snapshot, the pendingSnapshot will be set to the
|
||||
// index of the snapshot. If pendingSnapshot is set, the replication process of
|
||||
// this Progress will be paused. raft will not resend snapshot until the pending one
|
||||
// is reported to be failed.
|
||||
PendingSnapshot uint64
|
||||
|
||||
// RecentActive is true if the progress is recently active. Receiving any messages
|
||||
// from the corresponding follower indicates the progress is active.
|
||||
// RecentActive can be reset to false after an election timeout.
|
||||
RecentActive bool
|
||||
|
||||
// inflights is a sliding window for the inflight messages.
|
||||
// When inflights is full, no more message should be sent.
|
||||
// When a leader sends out a message, the index of the last
|
||||
// entry should be added to inflights. The index MUST be added
|
||||
// into inflights in order.
|
||||
// When a leader receives a reply, the previous inflights should
|
||||
// be freed by calling inflights.freeTo.
|
||||
ins *inflights
|
||||
}
|
||||
|
||||
func (pr *Progress) resetState(state ProgressStateType) {
|
||||
pr.Paused = false
|
||||
pr.RecentActive = false
|
||||
pr.PendingSnapshot = 0
|
||||
pr.State = state
|
||||
pr.ins.reset()
|
||||
}
|
||||
|
||||
func (pr *Progress) becomeProbe() {
|
||||
// If the original state is ProgressStateSnapshot, progress knows that
|
||||
// the pending snapshot has been sent to this peer successfully, then
|
||||
// probes from pendingSnapshot + 1.
|
||||
if pr.State == ProgressStateSnapshot {
|
||||
pendingSnapshot := pr.PendingSnapshot
|
||||
pr.resetState(ProgressStateProbe)
|
||||
pr.Next = max(pr.Match+1, pendingSnapshot+1)
|
||||
} else {
|
||||
pr.resetState(ProgressStateProbe)
|
||||
pr.Next = pr.Match + 1
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *Progress) becomeReplicate() {
|
||||
pr.resetState(ProgressStateReplicate)
|
||||
pr.Next = pr.Match + 1
|
||||
}
|
||||
|
||||
func (pr *Progress) becomeSnapshot(snapshoti uint64) {
|
||||
pr.resetState(ProgressStateSnapshot)
|
||||
pr.PendingSnapshot = snapshoti
|
||||
}
|
||||
|
||||
// maybeUpdate returns false if the given n index comes from an outdated message.
|
||||
// Otherwise it updates the progress and returns true.
|
||||
func (pr *Progress) maybeUpdate(n uint64) bool {
|
||||
var updated bool
|
||||
if pr.Match < n {
|
||||
pr.Match = n
|
||||
updated = true
|
||||
pr.resume()
|
||||
}
|
||||
if pr.Next < n+1 {
|
||||
pr.Next = n + 1
|
||||
}
|
||||
return updated
|
||||
}
|
||||
|
||||
func (pr *Progress) optimisticUpdate(n uint64) { pr.Next = n + 1 }
|
||||
|
||||
// maybeDecrTo returns false if the given to index comes from an out of order message.
|
||||
// Otherwise it decreases the progress next index to min(rejected, last) and returns true.
|
||||
func (pr *Progress) maybeDecrTo(rejected, last uint64) bool {
|
||||
if pr.State == ProgressStateReplicate {
|
||||
// the rejection must be stale if the progress has matched and "rejected"
|
||||
// is smaller than "match".
|
||||
if rejected <= pr.Match {
|
||||
return false
|
||||
}
|
||||
// directly decrease next to match + 1
|
||||
pr.Next = pr.Match + 1
|
||||
return true
|
||||
}
|
||||
|
||||
// the rejection must be stale if "rejected" does not match next - 1
|
||||
if pr.Next-1 != rejected {
|
||||
return false
|
||||
}
|
||||
|
||||
if pr.Next = min(rejected, last+1); pr.Next < 1 {
|
||||
pr.Next = 1
|
||||
}
|
||||
pr.resume()
|
||||
return true
|
||||
}
|
||||
|
||||
func (pr *Progress) pause() { pr.Paused = true }
|
||||
func (pr *Progress) resume() { pr.Paused = false }
|
||||
|
||||
// isPaused returns whether progress stops sending message.
|
||||
func (pr *Progress) isPaused() bool {
|
||||
switch pr.State {
|
||||
case ProgressStateProbe:
|
||||
return pr.Paused
|
||||
case ProgressStateReplicate:
|
||||
return pr.ins.full()
|
||||
case ProgressStateSnapshot:
|
||||
return true
|
||||
default:
|
||||
panic("unexpected state")
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *Progress) snapshotFailure() { pr.PendingSnapshot = 0 }
|
||||
|
||||
// maybeSnapshotAbort unsets pendingSnapshot if Match is equal or higher than
|
||||
// the pendingSnapshot
|
||||
func (pr *Progress) maybeSnapshotAbort() bool {
|
||||
return pr.State == ProgressStateSnapshot && pr.Match >= pr.PendingSnapshot
|
||||
}
|
||||
|
||||
func (pr *Progress) String() string {
|
||||
return fmt.Sprintf("next = %d, match = %d, state = %s, waiting = %v, pendingSnapshot = %d", pr.Next, pr.Match, pr.State, pr.isPaused(), pr.PendingSnapshot)
|
||||
}
|
||||
|
||||
type inflights struct {
|
||||
// the starting index in the buffer
|
||||
start int
|
||||
// number of inflights in the buffer
|
||||
count int
|
||||
|
||||
// the size of the buffer
|
||||
size int
|
||||
buffer []uint64
|
||||
}
|
||||
|
||||
func newInflights(size int) *inflights {
|
||||
return &inflights{
|
||||
size: size,
|
||||
buffer: make([]uint64, size),
|
||||
}
|
||||
}
|
||||
|
||||
// add adds an inflight into inflights
|
||||
func (in *inflights) add(inflight uint64) {
|
||||
if in.full() {
|
||||
panic("cannot add into a full inflights")
|
||||
}
|
||||
next := in.start + in.count
|
||||
if next >= in.size {
|
||||
next -= in.size
|
||||
}
|
||||
in.buffer[next] = inflight
|
||||
in.count++
|
||||
}
|
||||
|
||||
// freeTo frees the inflights smaller or equal to the given `to` flight.
|
||||
func (in *inflights) freeTo(to uint64) {
|
||||
if in.count == 0 || to < in.buffer[in.start] {
|
||||
// out of the left side of the window
|
||||
return
|
||||
}
|
||||
|
||||
i, idx := 0, in.start
|
||||
for i = 0; i < in.count; i++ {
|
||||
if to < in.buffer[idx] { // found the first large inflight
|
||||
break
|
||||
}
|
||||
|
||||
// increase index and maybe rotate
|
||||
if idx++; idx >= in.size {
|
||||
idx -= in.size
|
||||
}
|
||||
}
|
||||
// free i inflights and set new start index
|
||||
in.count -= i
|
||||
in.start = idx
|
||||
}
|
||||
|
||||
func (in *inflights) freeFirstOne() { in.freeTo(in.buffer[in.start]) }
|
||||
|
||||
// full returns true if the inflights is full.
|
||||
func (in *inflights) full() bool {
|
||||
return in.count == in.size
|
||||
}
|
||||
|
||||
// resets frees all inflights.
|
||||
func (in *inflights) reset() {
|
||||
in.count = 0
|
||||
in.start = 0
|
||||
}
|
||||
902
vendor/github.com/coreos/etcd/raft/raft.go
generated
vendored
Normal file
902
vendor/github.com/coreos/etcd/raft/raft.go
generated
vendored
Normal file
@@ -0,0 +1,902 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raft
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
// None is a placeholder node ID used when there is no leader.
|
||||
const None uint64 = 0
|
||||
const noLimit = math.MaxUint64
|
||||
|
||||
var ErrSnapshotTemporarilyUnavailable = errors.New("snapshot is temporarily unavailable")
|
||||
|
||||
// Possible values for StateType.
|
||||
const (
|
||||
StateFollower StateType = iota
|
||||
StateCandidate
|
||||
StateLeader
|
||||
)
|
||||
|
||||
// StateType represents the role of a node in a cluster.
|
||||
type StateType uint64
|
||||
|
||||
var stmap = [...]string{
|
||||
"StateFollower",
|
||||
"StateCandidate",
|
||||
"StateLeader",
|
||||
}
|
||||
|
||||
func (st StateType) String() string {
|
||||
return stmap[uint64(st)]
|
||||
}
|
||||
|
||||
// Config contains the parameters to start a raft.
|
||||
type Config struct {
|
||||
// ID is the identity of the local raft. ID cannot be 0.
|
||||
ID uint64
|
||||
|
||||
// peers contains the IDs of all nodes (including self) in the raft cluster. It
|
||||
// should only be set when starting a new raft cluster. Restarting raft from
|
||||
// previous configuration will panic if peers is set. peer is private and only
|
||||
// used for testing right now.
|
||||
peers []uint64
|
||||
|
||||
// ElectionTick is the number of Node.Tick invocations that must pass between
|
||||
// elections. That is, if a follower does not receive any message from the
|
||||
// leader of current term before ElectionTick has elapsed, it will become
|
||||
// candidate and start an election. ElectionTick must be greater than
|
||||
// HeartbeatTick. We suggest ElectionTick = 10 * HeartbeatTick to avoid
|
||||
// unnecessary leader switching.
|
||||
ElectionTick int
|
||||
// HeartbeatTick is the number of Node.Tick invocations that must pass between
|
||||
// heartbeats. That is, a leader sends heartbeat messages to maintain its
|
||||
// leadership every HeartbeatTick ticks.
|
||||
HeartbeatTick int
|
||||
|
||||
// Storage is the storage for raft. raft generates entries and states to be
|
||||
// stored in storage. raft reads the persisted entries and states out of
|
||||
// Storage when it needs. raft reads out the previous state and configuration
|
||||
// out of storage when restarting.
|
||||
Storage Storage
|
||||
// Applied is the last applied index. It should only be set when restarting
|
||||
// raft. raft will not return entries to the application smaller or equal to
|
||||
// Applied. If Applied is unset when restarting, raft might return previous
|
||||
// applied entries. This is a very application dependent configuration.
|
||||
Applied uint64
|
||||
|
||||
// MaxSizePerMsg limits the max size of each append message. Smaller value
|
||||
// lowers the raft recovery cost(initial probing and message lost during normal
|
||||
// operation). On the other side, it might affect the throughput during normal
|
||||
// replication. Note: math.MaxUint64 for unlimited, 0 for at most one entry per
|
||||
// message.
|
||||
MaxSizePerMsg uint64
|
||||
// MaxInflightMsgs limits the max number of in-flight append messages during
|
||||
// optimistic replication phase. The application transportation layer usually
|
||||
// has its own sending buffer over TCP/UDP. Setting MaxInflightMsgs to avoid
|
||||
// overflowing that sending buffer. TODO (xiangli): feedback to application to
|
||||
// limit the proposal rate?
|
||||
MaxInflightMsgs int
|
||||
|
||||
// CheckQuorum specifies if the leader should check quorum activity. Leader
|
||||
// steps down when quorum is not active for an electionTimeout.
|
||||
CheckQuorum bool
|
||||
|
||||
// Logger is the logger used for raft log. For multinode which can host
|
||||
// multiple raft group, each raft group can have its own logger
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
func (c *Config) validate() error {
|
||||
if c.ID == None {
|
||||
return errors.New("cannot use none as id")
|
||||
}
|
||||
|
||||
if c.HeartbeatTick <= 0 {
|
||||
return errors.New("heartbeat tick must be greater than 0")
|
||||
}
|
||||
|
||||
if c.ElectionTick <= c.HeartbeatTick {
|
||||
return errors.New("election tick must be greater than heartbeat tick")
|
||||
}
|
||||
|
||||
if c.Storage == nil {
|
||||
return errors.New("storage cannot be nil")
|
||||
}
|
||||
|
||||
if c.MaxInflightMsgs <= 0 {
|
||||
return errors.New("max inflight messages must be greater than 0")
|
||||
}
|
||||
|
||||
if c.Logger == nil {
|
||||
c.Logger = raftLogger
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type raft struct {
|
||||
id uint64
|
||||
|
||||
Term uint64
|
||||
Vote uint64
|
||||
|
||||
// the log
|
||||
raftLog *raftLog
|
||||
|
||||
maxInflight int
|
||||
maxMsgSize uint64
|
||||
prs map[uint64]*Progress
|
||||
|
||||
state StateType
|
||||
|
||||
votes map[uint64]bool
|
||||
|
||||
msgs []pb.Message
|
||||
|
||||
// the leader id
|
||||
lead uint64
|
||||
|
||||
// New configuration is ignored if there exists unapplied configuration.
|
||||
pendingConf bool
|
||||
|
||||
// number of ticks since it reached last electionTimeout when it is leader
|
||||
// or candidate.
|
||||
// number of ticks since it reached last electionTimeout or received a
|
||||
// valid message from current leader when it is a follower.
|
||||
electionElapsed int
|
||||
|
||||
// number of ticks since it reached last heartbeatTimeout.
|
||||
// only leader keeps heartbeatElapsed.
|
||||
heartbeatElapsed int
|
||||
|
||||
checkQuorum bool
|
||||
|
||||
heartbeatTimeout int
|
||||
electionTimeout int
|
||||
// randomizedElectionTimeout is a random number between
|
||||
// [electiontimeout, 2 * electiontimeout - 1]. It gets reset
|
||||
// when raft changes its state to follower or candidate.
|
||||
randomizedElectionTimeout int
|
||||
|
||||
rand *rand.Rand
|
||||
tick func()
|
||||
step stepFunc
|
||||
|
||||
logger Logger
|
||||
}
|
||||
|
||||
func newRaft(c *Config) *raft {
|
||||
if err := c.validate(); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
raftlog := newLog(c.Storage, c.Logger)
|
||||
hs, cs, err := c.Storage.InitialState()
|
||||
if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
peers := c.peers
|
||||
if len(cs.Nodes) > 0 {
|
||||
if len(peers) > 0 {
|
||||
// TODO(bdarnell): the peers argument is always nil except in
|
||||
// tests; the argument should be removed and these tests should be
|
||||
// updated to specify their nodes through a snapshot.
|
||||
panic("cannot specify both newRaft(peers) and ConfState.Nodes)")
|
||||
}
|
||||
peers = cs.Nodes
|
||||
}
|
||||
r := &raft{
|
||||
id: c.ID,
|
||||
lead: None,
|
||||
raftLog: raftlog,
|
||||
maxMsgSize: c.MaxSizePerMsg,
|
||||
maxInflight: c.MaxInflightMsgs,
|
||||
prs: make(map[uint64]*Progress),
|
||||
electionTimeout: c.ElectionTick,
|
||||
heartbeatTimeout: c.HeartbeatTick,
|
||||
logger: c.Logger,
|
||||
checkQuorum: c.CheckQuorum,
|
||||
}
|
||||
r.rand = rand.New(rand.NewSource(int64(c.ID)))
|
||||
for _, p := range peers {
|
||||
r.prs[p] = &Progress{Next: 1, ins: newInflights(r.maxInflight)}
|
||||
}
|
||||
if !isHardStateEqual(hs, emptyState) {
|
||||
r.loadState(hs)
|
||||
}
|
||||
if c.Applied > 0 {
|
||||
raftlog.appliedTo(c.Applied)
|
||||
}
|
||||
r.becomeFollower(r.Term, None)
|
||||
|
||||
nodesStrs := make([]string, 0)
|
||||
for _, n := range r.nodes() {
|
||||
nodesStrs = append(nodesStrs, fmt.Sprintf("%x", n))
|
||||
}
|
||||
|
||||
r.logger.Infof("newRaft %x [peers: [%s], term: %d, commit: %d, applied: %d, lastindex: %d, lastterm: %d]",
|
||||
r.id, strings.Join(nodesStrs, ","), r.Term, r.raftLog.committed, r.raftLog.applied, r.raftLog.lastIndex(), r.raftLog.lastTerm())
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *raft) hasLeader() bool { return r.lead != None }
|
||||
|
||||
func (r *raft) softState() *SoftState { return &SoftState{Lead: r.lead, RaftState: r.state} }
|
||||
|
||||
func (r *raft) hardState() pb.HardState {
|
||||
return pb.HardState{
|
||||
Term: r.Term,
|
||||
Vote: r.Vote,
|
||||
Commit: r.raftLog.committed,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *raft) quorum() int { return len(r.prs)/2 + 1 }
|
||||
|
||||
func (r *raft) nodes() []uint64 {
|
||||
nodes := make([]uint64, 0, len(r.prs))
|
||||
for id := range r.prs {
|
||||
nodes = append(nodes, id)
|
||||
}
|
||||
sort.Sort(uint64Slice(nodes))
|
||||
return nodes
|
||||
}
|
||||
|
||||
// send persists state to stable storage and then sends to its mailbox.
|
||||
func (r *raft) send(m pb.Message) {
|
||||
m.From = r.id
|
||||
// do not attach term to MsgProp
|
||||
// proposals are a way to forward to the leader and
|
||||
// should be treated as local message.
|
||||
if m.Type != pb.MsgProp {
|
||||
m.Term = r.Term
|
||||
}
|
||||
r.msgs = append(r.msgs, m)
|
||||
}
|
||||
|
||||
// sendAppend sends RPC, with entries to the given peer.
|
||||
func (r *raft) sendAppend(to uint64) {
|
||||
pr := r.prs[to]
|
||||
if pr.isPaused() {
|
||||
return
|
||||
}
|
||||
m := pb.Message{}
|
||||
m.To = to
|
||||
|
||||
term, errt := r.raftLog.term(pr.Next - 1)
|
||||
ents, erre := r.raftLog.entries(pr.Next, r.maxMsgSize)
|
||||
|
||||
if errt != nil || erre != nil { // send snapshot if we failed to get term or entries
|
||||
if !pr.RecentActive {
|
||||
r.logger.Debugf("ignore sending snapshot to %x since it is not recently active", to)
|
||||
return
|
||||
}
|
||||
|
||||
m.Type = pb.MsgSnap
|
||||
snapshot, err := r.raftLog.snapshot()
|
||||
if err != nil {
|
||||
if err == ErrSnapshotTemporarilyUnavailable {
|
||||
r.logger.Debugf("%x failed to send snapshot to %x because snapshot is temporarily unavailable", r.id, to)
|
||||
return
|
||||
}
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
if IsEmptySnap(snapshot) {
|
||||
panic("need non-empty snapshot")
|
||||
}
|
||||
m.Snapshot = snapshot
|
||||
sindex, sterm := snapshot.Metadata.Index, snapshot.Metadata.Term
|
||||
r.logger.Debugf("%x [firstindex: %d, commit: %d] sent snapshot[index: %d, term: %d] to %x [%s]",
|
||||
r.id, r.raftLog.firstIndex(), r.raftLog.committed, sindex, sterm, to, pr)
|
||||
pr.becomeSnapshot(sindex)
|
||||
r.logger.Debugf("%x paused sending replication messages to %x [%s]", r.id, to, pr)
|
||||
} else {
|
||||
m.Type = pb.MsgApp
|
||||
m.Index = pr.Next - 1
|
||||
m.LogTerm = term
|
||||
m.Entries = ents
|
||||
m.Commit = r.raftLog.committed
|
||||
if n := len(m.Entries); n != 0 {
|
||||
switch pr.State {
|
||||
// optimistically increase the next when in ProgressStateReplicate
|
||||
case ProgressStateReplicate:
|
||||
last := m.Entries[n-1].Index
|
||||
pr.optimisticUpdate(last)
|
||||
pr.ins.add(last)
|
||||
case ProgressStateProbe:
|
||||
pr.pause()
|
||||
default:
|
||||
r.logger.Panicf("%x is sending append in unhandled state %s", r.id, pr.State)
|
||||
}
|
||||
}
|
||||
}
|
||||
r.send(m)
|
||||
}
|
||||
|
||||
// sendHeartbeat sends an empty MsgApp
|
||||
func (r *raft) sendHeartbeat(to uint64) {
|
||||
// Attach the commit as min(to.matched, r.committed).
|
||||
// When the leader sends out heartbeat message,
|
||||
// the receiver(follower) might not be matched with the leader
|
||||
// or it might not have all the committed entries.
|
||||
// The leader MUST NOT forward the follower's commit to
|
||||
// an unmatched index.
|
||||
commit := min(r.prs[to].Match, r.raftLog.committed)
|
||||
m := pb.Message{
|
||||
To: to,
|
||||
Type: pb.MsgHeartbeat,
|
||||
Commit: commit,
|
||||
}
|
||||
r.send(m)
|
||||
}
|
||||
|
||||
// bcastAppend sends RPC, with entries to all peers that are not up-to-date
|
||||
// according to the progress recorded in r.prs.
|
||||
func (r *raft) bcastAppend() {
|
||||
for id := range r.prs {
|
||||
if id == r.id {
|
||||
continue
|
||||
}
|
||||
r.sendAppend(id)
|
||||
}
|
||||
}
|
||||
|
||||
// bcastHeartbeat sends RPC, without entries to all the peers.
|
||||
func (r *raft) bcastHeartbeat() {
|
||||
for id := range r.prs {
|
||||
if id == r.id {
|
||||
continue
|
||||
}
|
||||
r.sendHeartbeat(id)
|
||||
r.prs[id].resume()
|
||||
}
|
||||
}
|
||||
|
||||
// maybeCommit attempts to advance the commit index. Returns true if
|
||||
// the commit index changed (in which case the caller should call
|
||||
// r.bcastAppend).
|
||||
func (r *raft) maybeCommit() bool {
|
||||
// TODO(bmizerany): optimize.. Currently naive
|
||||
mis := make(uint64Slice, 0, len(r.prs))
|
||||
for id := range r.prs {
|
||||
mis = append(mis, r.prs[id].Match)
|
||||
}
|
||||
sort.Sort(sort.Reverse(mis))
|
||||
mci := mis[r.quorum()-1]
|
||||
return r.raftLog.maybeCommit(mci, r.Term)
|
||||
}
|
||||
|
||||
func (r *raft) reset(term uint64) {
|
||||
if r.Term != term {
|
||||
r.Term = term
|
||||
r.Vote = None
|
||||
}
|
||||
r.lead = None
|
||||
|
||||
r.electionElapsed = 0
|
||||
r.heartbeatElapsed = 0
|
||||
r.resetRandomizedElectionTimeout()
|
||||
|
||||
r.votes = make(map[uint64]bool)
|
||||
for id := range r.prs {
|
||||
r.prs[id] = &Progress{Next: r.raftLog.lastIndex() + 1, ins: newInflights(r.maxInflight)}
|
||||
if id == r.id {
|
||||
r.prs[id].Match = r.raftLog.lastIndex()
|
||||
}
|
||||
}
|
||||
r.pendingConf = false
|
||||
}
|
||||
|
||||
func (r *raft) appendEntry(es ...pb.Entry) {
|
||||
li := r.raftLog.lastIndex()
|
||||
for i := range es {
|
||||
es[i].Term = r.Term
|
||||
es[i].Index = li + 1 + uint64(i)
|
||||
}
|
||||
r.raftLog.append(es...)
|
||||
r.prs[r.id].maybeUpdate(r.raftLog.lastIndex())
|
||||
// Regardless of maybeCommit's return, our caller will call bcastAppend.
|
||||
r.maybeCommit()
|
||||
}
|
||||
|
||||
// tickElection is run by followers and candidates after r.electionTimeout.
|
||||
func (r *raft) tickElection() {
|
||||
if !r.promotable() {
|
||||
r.electionElapsed = 0
|
||||
return
|
||||
}
|
||||
r.electionElapsed++
|
||||
if r.pastElectionTimeout() {
|
||||
r.electionElapsed = 0
|
||||
r.Step(pb.Message{From: r.id, Type: pb.MsgHup})
|
||||
}
|
||||
}
|
||||
|
||||
// tickHeartbeat is run by leaders to send a MsgBeat after r.heartbeatTimeout.
|
||||
func (r *raft) tickHeartbeat() {
|
||||
r.heartbeatElapsed++
|
||||
r.electionElapsed++
|
||||
|
||||
if r.electionElapsed >= r.electionTimeout {
|
||||
r.electionElapsed = 0
|
||||
if r.checkQuorum {
|
||||
r.Step(pb.Message{From: r.id, Type: pb.MsgCheckQuorum})
|
||||
}
|
||||
}
|
||||
|
||||
if r.state != StateLeader {
|
||||
return
|
||||
}
|
||||
|
||||
if r.heartbeatElapsed >= r.heartbeatTimeout {
|
||||
r.heartbeatElapsed = 0
|
||||
r.Step(pb.Message{From: r.id, Type: pb.MsgBeat})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *raft) becomeFollower(term uint64, lead uint64) {
|
||||
r.step = stepFollower
|
||||
r.reset(term)
|
||||
r.tick = r.tickElection
|
||||
r.lead = lead
|
||||
r.state = StateFollower
|
||||
r.logger.Infof("%x became follower at term %d", r.id, r.Term)
|
||||
}
|
||||
|
||||
func (r *raft) becomeCandidate() {
|
||||
// TODO(xiangli) remove the panic when the raft implementation is stable
|
||||
if r.state == StateLeader {
|
||||
panic("invalid transition [leader -> candidate]")
|
||||
}
|
||||
r.step = stepCandidate
|
||||
r.reset(r.Term + 1)
|
||||
r.tick = r.tickElection
|
||||
r.Vote = r.id
|
||||
r.state = StateCandidate
|
||||
r.logger.Infof("%x became candidate at term %d", r.id, r.Term)
|
||||
}
|
||||
|
||||
func (r *raft) becomeLeader() {
|
||||
// TODO(xiangli) remove the panic when the raft implementation is stable
|
||||
if r.state == StateFollower {
|
||||
panic("invalid transition [follower -> leader]")
|
||||
}
|
||||
r.step = stepLeader
|
||||
r.reset(r.Term)
|
||||
r.tick = r.tickHeartbeat
|
||||
r.lead = r.id
|
||||
r.state = StateLeader
|
||||
ents, err := r.raftLog.entries(r.raftLog.committed+1, noLimit)
|
||||
if err != nil {
|
||||
r.logger.Panicf("unexpected error getting uncommitted entries (%v)", err)
|
||||
}
|
||||
|
||||
for _, e := range ents {
|
||||
if e.Type != pb.EntryConfChange {
|
||||
continue
|
||||
}
|
||||
if r.pendingConf {
|
||||
panic("unexpected double uncommitted config entry")
|
||||
}
|
||||
r.pendingConf = true
|
||||
}
|
||||
r.appendEntry(pb.Entry{Data: nil})
|
||||
r.logger.Infof("%x became leader at term %d", r.id, r.Term)
|
||||
}
|
||||
|
||||
func (r *raft) campaign() {
|
||||
r.becomeCandidate()
|
||||
if r.quorum() == r.poll(r.id, true) {
|
||||
r.becomeLeader()
|
||||
return
|
||||
}
|
||||
for id := range r.prs {
|
||||
if id == r.id {
|
||||
continue
|
||||
}
|
||||
r.logger.Infof("%x [logterm: %d, index: %d] sent vote request to %x at term %d",
|
||||
r.id, r.raftLog.lastTerm(), r.raftLog.lastIndex(), id, r.Term)
|
||||
r.send(pb.Message{To: id, Type: pb.MsgVote, Index: r.raftLog.lastIndex(), LogTerm: r.raftLog.lastTerm()})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *raft) poll(id uint64, v bool) (granted int) {
|
||||
if v {
|
||||
r.logger.Infof("%x received vote from %x at term %d", r.id, id, r.Term)
|
||||
} else {
|
||||
r.logger.Infof("%x received vote rejection from %x at term %d", r.id, id, r.Term)
|
||||
}
|
||||
if _, ok := r.votes[id]; !ok {
|
||||
r.votes[id] = v
|
||||
}
|
||||
for _, vv := range r.votes {
|
||||
if vv {
|
||||
granted++
|
||||
}
|
||||
}
|
||||
return granted
|
||||
}
|
||||
|
||||
func (r *raft) Step(m pb.Message) error {
|
||||
if m.Type == pb.MsgHup {
|
||||
if r.state != StateLeader {
|
||||
r.logger.Infof("%x is starting a new election at term %d", r.id, r.Term)
|
||||
r.campaign()
|
||||
} else {
|
||||
r.logger.Debugf("%x ignoring MsgHup because already leader", r.id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case m.Term == 0:
|
||||
// local message
|
||||
case m.Term > r.Term:
|
||||
lead := m.From
|
||||
if m.Type == pb.MsgVote {
|
||||
lead = None
|
||||
}
|
||||
r.logger.Infof("%x [term: %d] received a %s message with higher term from %x [term: %d]",
|
||||
r.id, r.Term, m.Type, m.From, m.Term)
|
||||
r.becomeFollower(m.Term, lead)
|
||||
case m.Term < r.Term:
|
||||
// ignore
|
||||
r.logger.Infof("%x [term: %d] ignored a %s message with lower term from %x [term: %d]",
|
||||
r.id, r.Term, m.Type, m.From, m.Term)
|
||||
return nil
|
||||
}
|
||||
r.step(r, m)
|
||||
return nil
|
||||
}
|
||||
|
||||
type stepFunc func(r *raft, m pb.Message)
|
||||
|
||||
func stepLeader(r *raft, m pb.Message) {
|
||||
|
||||
// These message types do not require any progress for m.From.
|
||||
switch m.Type {
|
||||
case pb.MsgBeat:
|
||||
r.bcastHeartbeat()
|
||||
return
|
||||
case pb.MsgCheckQuorum:
|
||||
if !r.checkQuorumActive() {
|
||||
r.logger.Warningf("%x stepped down to follower since quorum is not active", r.id)
|
||||
r.becomeFollower(r.Term, None)
|
||||
}
|
||||
return
|
||||
case pb.MsgProp:
|
||||
if len(m.Entries) == 0 {
|
||||
r.logger.Panicf("%x stepped empty MsgProp", r.id)
|
||||
}
|
||||
if _, ok := r.prs[r.id]; !ok {
|
||||
// If we are not currently a member of the range (i.e. this node
|
||||
// was removed from the configuration while serving as leader),
|
||||
// drop any new proposals.
|
||||
return
|
||||
}
|
||||
for i, e := range m.Entries {
|
||||
if e.Type == pb.EntryConfChange {
|
||||
if r.pendingConf {
|
||||
m.Entries[i] = pb.Entry{Type: pb.EntryNormal}
|
||||
}
|
||||
r.pendingConf = true
|
||||
}
|
||||
}
|
||||
r.appendEntry(m.Entries...)
|
||||
r.bcastAppend()
|
||||
return
|
||||
case pb.MsgVote:
|
||||
r.logger.Infof("%x [logterm: %d, index: %d, vote: %x] rejected vote from %x [logterm: %d, index: %d] at term %d",
|
||||
r.id, r.raftLog.lastTerm(), r.raftLog.lastIndex(), r.Vote, m.From, m.LogTerm, m.Index, r.Term)
|
||||
r.send(pb.Message{To: m.From, Type: pb.MsgVoteResp, Reject: true})
|
||||
return
|
||||
}
|
||||
|
||||
// All other message types require a progress for m.From (pr).
|
||||
pr, prOk := r.prs[m.From]
|
||||
if !prOk {
|
||||
r.logger.Debugf("no progress available for %x", m.From)
|
||||
return
|
||||
}
|
||||
switch m.Type {
|
||||
case pb.MsgAppResp:
|
||||
pr.RecentActive = true
|
||||
|
||||
if m.Reject {
|
||||
r.logger.Debugf("%x received msgApp rejection(lastindex: %d) from %x for index %d",
|
||||
r.id, m.RejectHint, m.From, m.Index)
|
||||
if pr.maybeDecrTo(m.Index, m.RejectHint) {
|
||||
r.logger.Debugf("%x decreased progress of %x to [%s]", r.id, m.From, pr)
|
||||
if pr.State == ProgressStateReplicate {
|
||||
pr.becomeProbe()
|
||||
}
|
||||
r.sendAppend(m.From)
|
||||
}
|
||||
} else {
|
||||
oldPaused := pr.isPaused()
|
||||
if pr.maybeUpdate(m.Index) {
|
||||
switch {
|
||||
case pr.State == ProgressStateProbe:
|
||||
pr.becomeReplicate()
|
||||
case pr.State == ProgressStateSnapshot && pr.maybeSnapshotAbort():
|
||||
r.logger.Debugf("%x snapshot aborted, resumed sending replication messages to %x [%s]", r.id, m.From, pr)
|
||||
pr.becomeProbe()
|
||||
case pr.State == ProgressStateReplicate:
|
||||
pr.ins.freeTo(m.Index)
|
||||
}
|
||||
|
||||
if r.maybeCommit() {
|
||||
r.bcastAppend()
|
||||
} else if oldPaused {
|
||||
// update() reset the wait state on this node. If we had delayed sending
|
||||
// an update before, send it now.
|
||||
r.sendAppend(m.From)
|
||||
}
|
||||
}
|
||||
}
|
||||
case pb.MsgHeartbeatResp:
|
||||
pr.RecentActive = true
|
||||
|
||||
// free one slot for the full inflights window to allow progress.
|
||||
if pr.State == ProgressStateReplicate && pr.ins.full() {
|
||||
pr.ins.freeFirstOne()
|
||||
}
|
||||
if pr.Match < r.raftLog.lastIndex() {
|
||||
r.sendAppend(m.From)
|
||||
}
|
||||
case pb.MsgSnapStatus:
|
||||
if pr.State != ProgressStateSnapshot {
|
||||
return
|
||||
}
|
||||
if !m.Reject {
|
||||
pr.becomeProbe()
|
||||
r.logger.Debugf("%x snapshot succeeded, resumed sending replication messages to %x [%s]", r.id, m.From, pr)
|
||||
} else {
|
||||
pr.snapshotFailure()
|
||||
pr.becomeProbe()
|
||||
r.logger.Debugf("%x snapshot failed, resumed sending replication messages to %x [%s]", r.id, m.From, pr)
|
||||
}
|
||||
// If snapshot finish, wait for the msgAppResp from the remote node before sending
|
||||
// out the next msgApp.
|
||||
// If snapshot failure, wait for a heartbeat interval before next try
|
||||
pr.pause()
|
||||
case pb.MsgUnreachable:
|
||||
// During optimistic replication, if the remote becomes unreachable,
|
||||
// there is huge probability that a MsgApp is lost.
|
||||
if pr.State == ProgressStateReplicate {
|
||||
pr.becomeProbe()
|
||||
}
|
||||
r.logger.Debugf("%x failed to send message to %x because it is unreachable [%s]", r.id, m.From, pr)
|
||||
}
|
||||
}
|
||||
|
||||
func stepCandidate(r *raft, m pb.Message) {
|
||||
switch m.Type {
|
||||
case pb.MsgProp:
|
||||
r.logger.Infof("%x no leader at term %d; dropping proposal", r.id, r.Term)
|
||||
return
|
||||
case pb.MsgApp:
|
||||
r.becomeFollower(r.Term, m.From)
|
||||
r.handleAppendEntries(m)
|
||||
case pb.MsgHeartbeat:
|
||||
r.becomeFollower(r.Term, m.From)
|
||||
r.handleHeartbeat(m)
|
||||
case pb.MsgSnap:
|
||||
r.becomeFollower(m.Term, m.From)
|
||||
r.handleSnapshot(m)
|
||||
case pb.MsgVote:
|
||||
r.logger.Infof("%x [logterm: %d, index: %d, vote: %x] rejected vote from %x [logterm: %d, index: %d] at term %d",
|
||||
r.id, r.raftLog.lastTerm(), r.raftLog.lastIndex(), r.Vote, m.From, m.LogTerm, m.Index, r.Term)
|
||||
r.send(pb.Message{To: m.From, Type: pb.MsgVoteResp, Reject: true})
|
||||
case pb.MsgVoteResp:
|
||||
gr := r.poll(m.From, !m.Reject)
|
||||
r.logger.Infof("%x [quorum:%d] has received %d votes and %d vote rejections", r.id, r.quorum(), gr, len(r.votes)-gr)
|
||||
switch r.quorum() {
|
||||
case gr:
|
||||
r.becomeLeader()
|
||||
r.bcastAppend()
|
||||
case len(r.votes) - gr:
|
||||
r.becomeFollower(r.Term, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stepFollower(r *raft, m pb.Message) {
|
||||
switch m.Type {
|
||||
case pb.MsgProp:
|
||||
if r.lead == None {
|
||||
r.logger.Infof("%x no leader at term %d; dropping proposal", r.id, r.Term)
|
||||
return
|
||||
}
|
||||
m.To = r.lead
|
||||
r.send(m)
|
||||
case pb.MsgApp:
|
||||
r.electionElapsed = 0
|
||||
r.lead = m.From
|
||||
r.handleAppendEntries(m)
|
||||
case pb.MsgHeartbeat:
|
||||
r.electionElapsed = 0
|
||||
r.lead = m.From
|
||||
r.handleHeartbeat(m)
|
||||
case pb.MsgSnap:
|
||||
r.electionElapsed = 0
|
||||
r.handleSnapshot(m)
|
||||
case pb.MsgVote:
|
||||
if (r.Vote == None || r.Vote == m.From) && r.raftLog.isUpToDate(m.Index, m.LogTerm) {
|
||||
r.electionElapsed = 0
|
||||
r.logger.Infof("%x [logterm: %d, index: %d, vote: %x] voted for %x [logterm: %d, index: %d] at term %d",
|
||||
r.id, r.raftLog.lastTerm(), r.raftLog.lastIndex(), r.Vote, m.From, m.LogTerm, m.Index, r.Term)
|
||||
r.Vote = m.From
|
||||
r.send(pb.Message{To: m.From, Type: pb.MsgVoteResp})
|
||||
} else {
|
||||
r.logger.Infof("%x [logterm: %d, index: %d, vote: %x] rejected vote from %x [logterm: %d, index: %d] at term %d",
|
||||
r.id, r.raftLog.lastTerm(), r.raftLog.lastIndex(), r.Vote, m.From, m.LogTerm, m.Index, r.Term)
|
||||
r.send(pb.Message{To: m.From, Type: pb.MsgVoteResp, Reject: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *raft) handleAppendEntries(m pb.Message) {
|
||||
if m.Index < r.raftLog.committed {
|
||||
r.send(pb.Message{To: m.From, Type: pb.MsgAppResp, Index: r.raftLog.committed})
|
||||
return
|
||||
}
|
||||
|
||||
if mlastIndex, ok := r.raftLog.maybeAppend(m.Index, m.LogTerm, m.Commit, m.Entries...); ok {
|
||||
r.send(pb.Message{To: m.From, Type: pb.MsgAppResp, Index: mlastIndex})
|
||||
} else {
|
||||
r.logger.Debugf("%x [logterm: %d, index: %d] rejected msgApp [logterm: %d, index: %d] from %x",
|
||||
r.id, r.raftLog.zeroTermOnErrCompacted(r.raftLog.term(m.Index)), m.Index, m.LogTerm, m.Index, m.From)
|
||||
r.send(pb.Message{To: m.From, Type: pb.MsgAppResp, Index: m.Index, Reject: true, RejectHint: r.raftLog.lastIndex()})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *raft) handleHeartbeat(m pb.Message) {
|
||||
r.raftLog.commitTo(m.Commit)
|
||||
r.send(pb.Message{To: m.From, Type: pb.MsgHeartbeatResp})
|
||||
}
|
||||
|
||||
func (r *raft) handleSnapshot(m pb.Message) {
|
||||
sindex, sterm := m.Snapshot.Metadata.Index, m.Snapshot.Metadata.Term
|
||||
if r.restore(m.Snapshot) {
|
||||
r.logger.Infof("%x [commit: %d] restored snapshot [index: %d, term: %d]",
|
||||
r.id, r.raftLog.committed, sindex, sterm)
|
||||
r.send(pb.Message{To: m.From, Type: pb.MsgAppResp, Index: r.raftLog.lastIndex()})
|
||||
} else {
|
||||
r.logger.Infof("%x [commit: %d] ignored snapshot [index: %d, term: %d]",
|
||||
r.id, r.raftLog.committed, sindex, sterm)
|
||||
r.send(pb.Message{To: m.From, Type: pb.MsgAppResp, Index: r.raftLog.committed})
|
||||
}
|
||||
}
|
||||
|
||||
// restore recovers the state machine from a snapshot. It restores the log and the
|
||||
// configuration of state machine.
|
||||
func (r *raft) restore(s pb.Snapshot) bool {
|
||||
if s.Metadata.Index <= r.raftLog.committed {
|
||||
return false
|
||||
}
|
||||
if r.raftLog.matchTerm(s.Metadata.Index, s.Metadata.Term) {
|
||||
r.logger.Infof("%x [commit: %d, lastindex: %d, lastterm: %d] fast-forwarded commit to snapshot [index: %d, term: %d]",
|
||||
r.id, r.raftLog.committed, r.raftLog.lastIndex(), r.raftLog.lastTerm(), s.Metadata.Index, s.Metadata.Term)
|
||||
r.raftLog.commitTo(s.Metadata.Index)
|
||||
return false
|
||||
}
|
||||
|
||||
r.logger.Infof("%x [commit: %d, lastindex: %d, lastterm: %d] starts to restore snapshot [index: %d, term: %d]",
|
||||
r.id, r.raftLog.committed, r.raftLog.lastIndex(), r.raftLog.lastTerm(), s.Metadata.Index, s.Metadata.Term)
|
||||
|
||||
r.raftLog.restore(s)
|
||||
r.prs = make(map[uint64]*Progress)
|
||||
for _, n := range s.Metadata.ConfState.Nodes {
|
||||
match, next := uint64(0), uint64(r.raftLog.lastIndex())+1
|
||||
if n == r.id {
|
||||
match = next - 1
|
||||
} else {
|
||||
match = 0
|
||||
}
|
||||
r.setProgress(n, match, next)
|
||||
r.logger.Infof("%x restored progress of %x [%s]", r.id, n, r.prs[n])
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// promotable indicates whether state machine can be promoted to leader,
|
||||
// which is true when its own id is in progress list.
|
||||
func (r *raft) promotable() bool {
|
||||
_, ok := r.prs[r.id]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (r *raft) addNode(id uint64) {
|
||||
if _, ok := r.prs[id]; ok {
|
||||
// Ignore any redundant addNode calls (which can happen because the
|
||||
// initial bootstrapping entries are applied twice).
|
||||
return
|
||||
}
|
||||
|
||||
r.setProgress(id, 0, r.raftLog.lastIndex()+1)
|
||||
r.pendingConf = false
|
||||
}
|
||||
|
||||
func (r *raft) removeNode(id uint64) {
|
||||
r.delProgress(id)
|
||||
r.pendingConf = false
|
||||
// The quorum size is now smaller, so see if any pending entries can
|
||||
// be committed.
|
||||
if r.maybeCommit() {
|
||||
r.bcastAppend()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *raft) resetPendingConf() { r.pendingConf = false }
|
||||
|
||||
func (r *raft) setProgress(id, match, next uint64) {
|
||||
r.prs[id] = &Progress{Next: next, Match: match, ins: newInflights(r.maxInflight)}
|
||||
}
|
||||
|
||||
func (r *raft) delProgress(id uint64) {
|
||||
delete(r.prs, id)
|
||||
}
|
||||
|
||||
func (r *raft) loadState(state pb.HardState) {
|
||||
if state.Commit < r.raftLog.committed || state.Commit > r.raftLog.lastIndex() {
|
||||
r.logger.Panicf("%x state.commit %d is out of range [%d, %d]", r.id, state.Commit, r.raftLog.committed, r.raftLog.lastIndex())
|
||||
}
|
||||
r.raftLog.committed = state.Commit
|
||||
r.Term = state.Term
|
||||
r.Vote = state.Vote
|
||||
}
|
||||
|
||||
// pastElectionTimeout returns true iff r.electionElapsed is greater
|
||||
// than or equal to the randomized election timeout in
|
||||
// [electiontimeout, 2 * electiontimeout - 1].
|
||||
func (r *raft) pastElectionTimeout() bool {
|
||||
return r.electionElapsed >= r.randomizedElectionTimeout
|
||||
}
|
||||
|
||||
func (r *raft) resetRandomizedElectionTimeout() {
|
||||
r.randomizedElectionTimeout = r.electionTimeout + r.rand.Intn(r.electionTimeout)
|
||||
}
|
||||
|
||||
// checkQuorumActive returns true if the quorum is active from
|
||||
// the view of the local raft state machine. Otherwise, it returns
|
||||
// false.
|
||||
// checkQuorumActive also resets all RecentActive to false.
|
||||
func (r *raft) checkQuorumActive() bool {
|
||||
var act int
|
||||
|
||||
for id := range r.prs {
|
||||
if id == r.id { // self is always active
|
||||
act++
|
||||
continue
|
||||
}
|
||||
|
||||
if r.prs[id].RecentActive {
|
||||
act++
|
||||
}
|
||||
|
||||
r.prs[id].RecentActive = false
|
||||
}
|
||||
|
||||
return act >= r.quorum()
|
||||
}
|
||||
1768
vendor/github.com/coreos/etcd/raft/raftpb/raft.pb.go
generated
vendored
Normal file
1768
vendor/github.com/coreos/etcd/raft/raftpb/raft.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
86
vendor/github.com/coreos/etcd/raft/raftpb/raft.proto
generated
vendored
Normal file
86
vendor/github.com/coreos/etcd/raft/raftpb/raft.proto
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
syntax = "proto2";
|
||||
package raftpb;
|
||||
|
||||
import "gogoproto/gogo.proto";
|
||||
|
||||
option (gogoproto.marshaler_all) = true;
|
||||
option (gogoproto.sizer_all) = true;
|
||||
option (gogoproto.unmarshaler_all) = true;
|
||||
option (gogoproto.goproto_getters_all) = false;
|
||||
option (gogoproto.goproto_enum_prefix_all) = false;
|
||||
|
||||
enum EntryType {
|
||||
EntryNormal = 0;
|
||||
EntryConfChange = 1;
|
||||
}
|
||||
|
||||
message Entry {
|
||||
optional EntryType Type = 1 [(gogoproto.nullable) = false];
|
||||
optional uint64 Term = 2 [(gogoproto.nullable) = false];
|
||||
optional uint64 Index = 3 [(gogoproto.nullable) = false];
|
||||
optional bytes Data = 4;
|
||||
}
|
||||
|
||||
message SnapshotMetadata {
|
||||
optional ConfState conf_state = 1 [(gogoproto.nullable) = false];
|
||||
optional uint64 index = 2 [(gogoproto.nullable) = false];
|
||||
optional uint64 term = 3 [(gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
message Snapshot {
|
||||
optional bytes data = 1;
|
||||
optional SnapshotMetadata metadata = 2 [(gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
enum MessageType {
|
||||
MsgHup = 0;
|
||||
MsgBeat = 1;
|
||||
MsgProp = 2;
|
||||
MsgApp = 3;
|
||||
MsgAppResp = 4;
|
||||
MsgVote = 5;
|
||||
MsgVoteResp = 6;
|
||||
MsgSnap = 7;
|
||||
MsgHeartbeat = 8;
|
||||
MsgHeartbeatResp = 9;
|
||||
MsgUnreachable = 10;
|
||||
MsgSnapStatus = 11;
|
||||
MsgCheckQuorum = 12;
|
||||
}
|
||||
|
||||
message Message {
|
||||
optional MessageType type = 1 [(gogoproto.nullable) = false];
|
||||
optional uint64 to = 2 [(gogoproto.nullable) = false];
|
||||
optional uint64 from = 3 [(gogoproto.nullable) = false];
|
||||
optional uint64 term = 4 [(gogoproto.nullable) = false];
|
||||
optional uint64 logTerm = 5 [(gogoproto.nullable) = false];
|
||||
optional uint64 index = 6 [(gogoproto.nullable) = false];
|
||||
repeated Entry entries = 7 [(gogoproto.nullable) = false];
|
||||
optional uint64 commit = 8 [(gogoproto.nullable) = false];
|
||||
optional Snapshot snapshot = 9 [(gogoproto.nullable) = false];
|
||||
optional bool reject = 10 [(gogoproto.nullable) = false];
|
||||
optional uint64 rejectHint = 11 [(gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
message HardState {
|
||||
optional uint64 term = 1 [(gogoproto.nullable) = false];
|
||||
optional uint64 vote = 2 [(gogoproto.nullable) = false];
|
||||
optional uint64 commit = 3 [(gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
message ConfState {
|
||||
repeated uint64 nodes = 1;
|
||||
}
|
||||
|
||||
enum ConfChangeType {
|
||||
ConfChangeAddNode = 0;
|
||||
ConfChangeRemoveNode = 1;
|
||||
ConfChangeUpdateNode = 2;
|
||||
}
|
||||
|
||||
message ConfChange {
|
||||
optional uint64 ID = 1 [(gogoproto.nullable) = false];
|
||||
optional ConfChangeType Type = 2 [(gogoproto.nullable) = false];
|
||||
optional uint64 NodeID = 3 [(gogoproto.nullable) = false];
|
||||
optional bytes Context = 4;
|
||||
}
|
||||
16
vendor/github.com/coreos/etcd/raft/rafttest/doc.go
generated
vendored
Normal file
16
vendor/github.com/coreos/etcd/raft/rafttest/doc.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package rafttest provides functional tests for etcd's raft implementation.
|
||||
package rafttest
|
||||
171
vendor/github.com/coreos/etcd/raft/rafttest/network.go
generated
vendored
Normal file
171
vendor/github.com/coreos/etcd/raft/rafttest/network.go
generated
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rafttest
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
// a network interface
|
||||
type iface interface {
|
||||
send(m raftpb.Message)
|
||||
recv() chan raftpb.Message
|
||||
disconnect()
|
||||
connect()
|
||||
}
|
||||
|
||||
// a network
|
||||
type network interface {
|
||||
// drop message at given rate (1.0 drops all messages)
|
||||
drop(from, to uint64, rate float64)
|
||||
// delay message for (0, d] randomly at given rate (1.0 delay all messages)
|
||||
// do we need rate here?
|
||||
delay(from, to uint64, d time.Duration, rate float64)
|
||||
disconnect(id uint64)
|
||||
connect(id uint64)
|
||||
// heal heals the network
|
||||
heal()
|
||||
}
|
||||
|
||||
type raftNetwork struct {
|
||||
mu sync.Mutex
|
||||
disconnected map[uint64]bool
|
||||
dropmap map[conn]float64
|
||||
delaymap map[conn]delay
|
||||
recvQueues map[uint64]chan raftpb.Message
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
from, to uint64
|
||||
}
|
||||
|
||||
type delay struct {
|
||||
d time.Duration
|
||||
rate float64
|
||||
}
|
||||
|
||||
func newRaftNetwork(nodes ...uint64) *raftNetwork {
|
||||
pn := &raftNetwork{
|
||||
recvQueues: make(map[uint64]chan raftpb.Message),
|
||||
dropmap: make(map[conn]float64),
|
||||
delaymap: make(map[conn]delay),
|
||||
disconnected: make(map[uint64]bool),
|
||||
}
|
||||
|
||||
for _, n := range nodes {
|
||||
pn.recvQueues[n] = make(chan raftpb.Message, 1024)
|
||||
}
|
||||
return pn
|
||||
}
|
||||
|
||||
func (rn *raftNetwork) nodeNetwork(id uint64) iface {
|
||||
return &nodeNetwork{id: id, raftNetwork: rn}
|
||||
}
|
||||
|
||||
func (rn *raftNetwork) send(m raftpb.Message) {
|
||||
rn.mu.Lock()
|
||||
to := rn.recvQueues[m.To]
|
||||
if rn.disconnected[m.To] {
|
||||
to = nil
|
||||
}
|
||||
drop := rn.dropmap[conn{m.From, m.To}]
|
||||
dl := rn.delaymap[conn{m.From, m.To}]
|
||||
rn.mu.Unlock()
|
||||
|
||||
if to == nil {
|
||||
return
|
||||
}
|
||||
if drop != 0 && rand.Float64() < drop {
|
||||
return
|
||||
}
|
||||
// TODO: shall we dl without blocking the send call?
|
||||
if dl.d != 0 && rand.Float64() < dl.rate {
|
||||
rd := rand.Int63n(int64(dl.d))
|
||||
time.Sleep(time.Duration(rd))
|
||||
}
|
||||
|
||||
select {
|
||||
case to <- m:
|
||||
default:
|
||||
// drop messages when the receiver queue is full.
|
||||
}
|
||||
}
|
||||
|
||||
func (rn *raftNetwork) recvFrom(from uint64) chan raftpb.Message {
|
||||
rn.mu.Lock()
|
||||
fromc := rn.recvQueues[from]
|
||||
if rn.disconnected[from] {
|
||||
fromc = nil
|
||||
}
|
||||
rn.mu.Unlock()
|
||||
|
||||
return fromc
|
||||
}
|
||||
|
||||
func (rn *raftNetwork) drop(from, to uint64, rate float64) {
|
||||
rn.mu.Lock()
|
||||
defer rn.mu.Unlock()
|
||||
rn.dropmap[conn{from, to}] = rate
|
||||
}
|
||||
|
||||
func (rn *raftNetwork) delay(from, to uint64, d time.Duration, rate float64) {
|
||||
rn.mu.Lock()
|
||||
defer rn.mu.Unlock()
|
||||
rn.delaymap[conn{from, to}] = delay{d, rate}
|
||||
}
|
||||
|
||||
func (rn *raftNetwork) heal() {
|
||||
rn.mu.Lock()
|
||||
defer rn.mu.Unlock()
|
||||
rn.dropmap = make(map[conn]float64)
|
||||
rn.delaymap = make(map[conn]delay)
|
||||
}
|
||||
|
||||
func (rn *raftNetwork) disconnect(id uint64) {
|
||||
rn.mu.Lock()
|
||||
defer rn.mu.Unlock()
|
||||
rn.disconnected[id] = true
|
||||
}
|
||||
|
||||
func (rn *raftNetwork) connect(id uint64) {
|
||||
rn.mu.Lock()
|
||||
defer rn.mu.Unlock()
|
||||
rn.disconnected[id] = false
|
||||
}
|
||||
|
||||
type nodeNetwork struct {
|
||||
id uint64
|
||||
*raftNetwork
|
||||
}
|
||||
|
||||
func (nt *nodeNetwork) connect() {
|
||||
nt.raftNetwork.connect(nt.id)
|
||||
}
|
||||
|
||||
func (nt *nodeNetwork) disconnect() {
|
||||
nt.raftNetwork.disconnect(nt.id)
|
||||
}
|
||||
|
||||
func (nt *nodeNetwork) send(m raftpb.Message) {
|
||||
nt.raftNetwork.send(m)
|
||||
}
|
||||
|
||||
func (nt *nodeNetwork) recv() chan raftpb.Message {
|
||||
return nt.recvFrom(nt.id)
|
||||
}
|
||||
145
vendor/github.com/coreos/etcd/raft/rafttest/node.go
generated
vendored
Normal file
145
vendor/github.com/coreos/etcd/raft/rafttest/node.go
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rafttest
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/raft"
|
||||
"github.com/coreos/etcd/raft/raftpb"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type node struct {
|
||||
raft.Node
|
||||
id uint64
|
||||
iface iface
|
||||
stopc chan struct{}
|
||||
pausec chan bool
|
||||
|
||||
// stable
|
||||
storage *raft.MemoryStorage
|
||||
state raftpb.HardState
|
||||
}
|
||||
|
||||
func startNode(id uint64, peers []raft.Peer, iface iface) *node {
|
||||
st := raft.NewMemoryStorage()
|
||||
c := &raft.Config{
|
||||
ID: id,
|
||||
ElectionTick: 10,
|
||||
HeartbeatTick: 1,
|
||||
Storage: st,
|
||||
MaxSizePerMsg: 1024 * 1024,
|
||||
MaxInflightMsgs: 256,
|
||||
}
|
||||
rn := raft.StartNode(c, peers)
|
||||
n := &node{
|
||||
Node: rn,
|
||||
id: id,
|
||||
storage: st,
|
||||
iface: iface,
|
||||
pausec: make(chan bool),
|
||||
}
|
||||
n.start()
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *node) start() {
|
||||
n.stopc = make(chan struct{})
|
||||
ticker := time.Tick(5 * time.Millisecond)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker:
|
||||
n.Tick()
|
||||
case rd := <-n.Ready():
|
||||
if !raft.IsEmptyHardState(rd.HardState) {
|
||||
n.state = rd.HardState
|
||||
n.storage.SetHardState(n.state)
|
||||
}
|
||||
n.storage.Append(rd.Entries)
|
||||
time.Sleep(time.Millisecond)
|
||||
// TODO: make send async, more like real world...
|
||||
for _, m := range rd.Messages {
|
||||
n.iface.send(m)
|
||||
}
|
||||
n.Advance()
|
||||
case m := <-n.iface.recv():
|
||||
n.Step(context.TODO(), m)
|
||||
case <-n.stopc:
|
||||
n.Stop()
|
||||
log.Printf("raft.%d: stop", n.id)
|
||||
n.Node = nil
|
||||
close(n.stopc)
|
||||
return
|
||||
case p := <-n.pausec:
|
||||
recvms := make([]raftpb.Message, 0)
|
||||
for p {
|
||||
select {
|
||||
case m := <-n.iface.recv():
|
||||
recvms = append(recvms, m)
|
||||
case p = <-n.pausec:
|
||||
}
|
||||
}
|
||||
// step all pending messages
|
||||
for _, m := range recvms {
|
||||
n.Step(context.TODO(), m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// stop stops the node. stop a stopped node might panic.
|
||||
// All in memory state of node is discarded.
|
||||
// All stable MUST be unchanged.
|
||||
func (n *node) stop() {
|
||||
n.iface.disconnect()
|
||||
n.stopc <- struct{}{}
|
||||
// wait for the shutdown
|
||||
<-n.stopc
|
||||
}
|
||||
|
||||
// restart restarts the node. restart a started node
|
||||
// blocks and might affect the future stop operation.
|
||||
func (n *node) restart() {
|
||||
// wait for the shutdown
|
||||
<-n.stopc
|
||||
c := &raft.Config{
|
||||
ID: n.id,
|
||||
ElectionTick: 10,
|
||||
HeartbeatTick: 1,
|
||||
Storage: n.storage,
|
||||
MaxSizePerMsg: 1024 * 1024,
|
||||
MaxInflightMsgs: 256,
|
||||
}
|
||||
n.Node = raft.RestartNode(c)
|
||||
n.start()
|
||||
n.iface.connect()
|
||||
}
|
||||
|
||||
// pause pauses the node.
|
||||
// The paused node buffers the received messages and replies
|
||||
// all of them when it resumes.
|
||||
func (n *node) pause() {
|
||||
n.pausec <- true
|
||||
}
|
||||
|
||||
// resume resumes the paused node.
|
||||
func (n *node) resume() {
|
||||
n.pausec <- false
|
||||
}
|
||||
228
vendor/github.com/coreos/etcd/raft/rawnode.go
generated
vendored
Normal file
228
vendor/github.com/coreos/etcd/raft/rawnode.go
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raft
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
// ErrStepLocalMsg is returned when try to step a local raft message
|
||||
var ErrStepLocalMsg = errors.New("raft: cannot step raft local message")
|
||||
|
||||
// ErrStepPeerNotFound is returned when try to step a response message
|
||||
// but there is no peer found in raft.prs for that node.
|
||||
var ErrStepPeerNotFound = errors.New("raft: cannot step as peer not found")
|
||||
|
||||
// RawNode is a thread-unsafe Node.
|
||||
// The methods of this struct correspond to the methods of Node and are described
|
||||
// more fully there.
|
||||
type RawNode struct {
|
||||
raft *raft
|
||||
prevSoftSt *SoftState
|
||||
prevHardSt pb.HardState
|
||||
}
|
||||
|
||||
func (rn *RawNode) newReady() Ready {
|
||||
return newReady(rn.raft, rn.prevSoftSt, rn.prevHardSt)
|
||||
}
|
||||
|
||||
func (rn *RawNode) commitReady(rd Ready) {
|
||||
if rd.SoftState != nil {
|
||||
rn.prevSoftSt = rd.SoftState
|
||||
}
|
||||
if !IsEmptyHardState(rd.HardState) {
|
||||
rn.prevHardSt = rd.HardState
|
||||
}
|
||||
if rn.prevHardSt.Commit != 0 {
|
||||
// In most cases, prevHardSt and rd.HardState will be the same
|
||||
// because when there are new entries to apply we just sent a
|
||||
// HardState with an updated Commit value. However, on initial
|
||||
// startup the two are different because we don't send a HardState
|
||||
// until something changes, but we do send any un-applied but
|
||||
// committed entries (and previously-committed entries may be
|
||||
// incorporated into the snapshot, even if rd.CommittedEntries is
|
||||
// empty). Therefore we mark all committed entries as applied
|
||||
// whether they were included in rd.HardState or not.
|
||||
rn.raft.raftLog.appliedTo(rn.prevHardSt.Commit)
|
||||
}
|
||||
if len(rd.Entries) > 0 {
|
||||
e := rd.Entries[len(rd.Entries)-1]
|
||||
rn.raft.raftLog.stableTo(e.Index, e.Term)
|
||||
}
|
||||
if !IsEmptySnap(rd.Snapshot) {
|
||||
rn.raft.raftLog.stableSnapTo(rd.Snapshot.Metadata.Index)
|
||||
}
|
||||
}
|
||||
|
||||
// NewRawNode returns a new RawNode given configuration and a list of raft peers.
|
||||
func NewRawNode(config *Config, peers []Peer) (*RawNode, error) {
|
||||
if config.ID == 0 {
|
||||
panic("config.ID must not be zero")
|
||||
}
|
||||
r := newRaft(config)
|
||||
rn := &RawNode{
|
||||
raft: r,
|
||||
}
|
||||
lastIndex, err := config.Storage.LastIndex()
|
||||
if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
// If the log is empty, this is a new RawNode (like StartNode); otherwise it's
|
||||
// restoring an existing RawNode (like RestartNode).
|
||||
// TODO(bdarnell): rethink RawNode initialization and whether the application needs
|
||||
// to be able to tell us when it expects the RawNode to exist.
|
||||
if lastIndex == 0 {
|
||||
r.becomeFollower(1, None)
|
||||
ents := make([]pb.Entry, len(peers))
|
||||
for i, peer := range peers {
|
||||
cc := pb.ConfChange{Type: pb.ConfChangeAddNode, NodeID: peer.ID, Context: peer.Context}
|
||||
data, err := cc.Marshal()
|
||||
if err != nil {
|
||||
panic("unexpected marshal error")
|
||||
}
|
||||
|
||||
ents[i] = pb.Entry{Type: pb.EntryConfChange, Term: 1, Index: uint64(i + 1), Data: data}
|
||||
}
|
||||
r.raftLog.append(ents...)
|
||||
r.raftLog.committed = uint64(len(ents))
|
||||
for _, peer := range peers {
|
||||
r.addNode(peer.ID)
|
||||
}
|
||||
}
|
||||
// Set the initial hard and soft states after performing all initialization.
|
||||
rn.prevSoftSt = r.softState()
|
||||
rn.prevHardSt = r.hardState()
|
||||
|
||||
return rn, nil
|
||||
}
|
||||
|
||||
// Tick advances the internal logical clock by a single tick.
|
||||
func (rn *RawNode) Tick() {
|
||||
rn.raft.tick()
|
||||
}
|
||||
|
||||
// Campaign causes this RawNode to transition to candidate state.
|
||||
func (rn *RawNode) Campaign() error {
|
||||
return rn.raft.Step(pb.Message{
|
||||
Type: pb.MsgHup,
|
||||
})
|
||||
}
|
||||
|
||||
// Propose proposes data be appended to the raft log.
|
||||
func (rn *RawNode) Propose(data []byte) error {
|
||||
return rn.raft.Step(pb.Message{
|
||||
Type: pb.MsgProp,
|
||||
From: rn.raft.id,
|
||||
Entries: []pb.Entry{
|
||||
{Data: data},
|
||||
}})
|
||||
}
|
||||
|
||||
// ProposeConfChange proposes a config change.
|
||||
func (rn *RawNode) ProposeConfChange(cc pb.ConfChange) error {
|
||||
data, err := cc.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return rn.raft.Step(pb.Message{
|
||||
Type: pb.MsgProp,
|
||||
Entries: []pb.Entry{
|
||||
{Type: pb.EntryConfChange, Data: data},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ApplyConfChange applies a config change to the local node.
|
||||
func (rn *RawNode) ApplyConfChange(cc pb.ConfChange) *pb.ConfState {
|
||||
if cc.NodeID == None {
|
||||
rn.raft.resetPendingConf()
|
||||
return &pb.ConfState{Nodes: rn.raft.nodes()}
|
||||
}
|
||||
switch cc.Type {
|
||||
case pb.ConfChangeAddNode:
|
||||
rn.raft.addNode(cc.NodeID)
|
||||
case pb.ConfChangeRemoveNode:
|
||||
rn.raft.removeNode(cc.NodeID)
|
||||
case pb.ConfChangeUpdateNode:
|
||||
rn.raft.resetPendingConf()
|
||||
default:
|
||||
panic("unexpected conf type")
|
||||
}
|
||||
return &pb.ConfState{Nodes: rn.raft.nodes()}
|
||||
}
|
||||
|
||||
// Step advances the state machine using the given message.
|
||||
func (rn *RawNode) Step(m pb.Message) error {
|
||||
// ignore unexpected local messages receiving over network
|
||||
if IsLocalMsg(m) {
|
||||
return ErrStepLocalMsg
|
||||
}
|
||||
if _, ok := rn.raft.prs[m.From]; ok || !IsResponseMsg(m) {
|
||||
return rn.raft.Step(m)
|
||||
}
|
||||
return ErrStepPeerNotFound
|
||||
}
|
||||
|
||||
// Ready returns the current point-in-time state of this RawNode.
|
||||
func (rn *RawNode) Ready() Ready {
|
||||
rd := rn.newReady()
|
||||
rn.raft.msgs = nil
|
||||
return rd
|
||||
}
|
||||
|
||||
// HasReady called when RawNode user need to check if any Ready pending.
|
||||
// Checking logic in this method should be consistent with Ready.containsUpdates().
|
||||
func (rn *RawNode) HasReady() bool {
|
||||
r := rn.raft
|
||||
if !r.softState().equal(rn.prevSoftSt) {
|
||||
return true
|
||||
}
|
||||
if hardSt := r.hardState(); !IsEmptyHardState(hardSt) && !isHardStateEqual(hardSt, rn.prevHardSt) {
|
||||
return true
|
||||
}
|
||||
if r.raftLog.unstable.snapshot != nil && !IsEmptySnap(*r.raftLog.unstable.snapshot) {
|
||||
return true
|
||||
}
|
||||
if len(r.msgs) > 0 || len(r.raftLog.unstableEntries()) > 0 || r.raftLog.hasNextEnts() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Advance notifies the RawNode that the application has applied and saved progress in the
|
||||
// last Ready results.
|
||||
func (rn *RawNode) Advance(rd Ready) {
|
||||
rn.commitReady(rd)
|
||||
}
|
||||
|
||||
// Status returns the current status of the given group.
|
||||
func (rn *RawNode) Status() *Status {
|
||||
status := getStatus(rn.raft)
|
||||
return &status
|
||||
}
|
||||
|
||||
// ReportUnreachable reports the given node is not reachable for the last send.
|
||||
func (rn *RawNode) ReportUnreachable(id uint64) {
|
||||
_ = rn.raft.Step(pb.Message{Type: pb.MsgUnreachable, From: id})
|
||||
}
|
||||
|
||||
// ReportSnapshot reports the status of the sent snapshot.
|
||||
func (rn *RawNode) ReportSnapshot(id uint64, status SnapshotStatus) {
|
||||
rej := status == SnapshotFailure
|
||||
|
||||
_ = rn.raft.Step(pb.Message{Type: pb.MsgSnapStatus, From: id, Reject: rej})
|
||||
}
|
||||
76
vendor/github.com/coreos/etcd/raft/status.go
generated
vendored
Normal file
76
vendor/github.com/coreos/etcd/raft/status.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
ID uint64
|
||||
|
||||
pb.HardState
|
||||
SoftState
|
||||
|
||||
Applied uint64
|
||||
Progress map[uint64]Progress
|
||||
}
|
||||
|
||||
// getStatus gets a copy of the current raft status.
|
||||
func getStatus(r *raft) Status {
|
||||
s := Status{ID: r.id}
|
||||
s.HardState = r.hardState()
|
||||
s.SoftState = *r.softState()
|
||||
|
||||
s.Applied = r.raftLog.applied
|
||||
|
||||
if s.RaftState == StateLeader {
|
||||
s.Progress = make(map[uint64]Progress)
|
||||
for id, p := range r.prs {
|
||||
s.Progress[id] = *p
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// MarshalJSON translates the raft status into JSON.
|
||||
// TODO: try to simplify this by introducing ID type into raft
|
||||
func (s Status) MarshalJSON() ([]byte, error) {
|
||||
j := fmt.Sprintf(`{"id":"%x","term":%d,"vote":"%x","commit":%d,"lead":"%x","raftState":%q,"progress":{`,
|
||||
s.ID, s.Term, s.Vote, s.Commit, s.Lead, s.RaftState)
|
||||
|
||||
if len(s.Progress) == 0 {
|
||||
j += "}}"
|
||||
} else {
|
||||
for k, v := range s.Progress {
|
||||
subj := fmt.Sprintf(`"%x":{"match":%d,"next":%d,"state":%q},`, k, v.Match, v.Next, v.State)
|
||||
j += subj
|
||||
}
|
||||
// remove the trailing ","
|
||||
j = j[:len(j)-1] + "}}"
|
||||
}
|
||||
return []byte(j), nil
|
||||
}
|
||||
|
||||
func (s Status) String() string {
|
||||
b, err := s.MarshalJSON()
|
||||
if err != nil {
|
||||
raftLogger.Panicf("unexpected error: %v", err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
252
vendor/github.com/coreos/etcd/raft/storage.go
generated
vendored
Normal file
252
vendor/github.com/coreos/etcd/raft/storage.go
generated
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raft
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
// ErrCompacted is returned by Storage.Entries/Compact when a requested
|
||||
// index is unavailable because it predates the last snapshot.
|
||||
var ErrCompacted = errors.New("requested index is unavailable due to compaction")
|
||||
|
||||
// ErrSnapOutOfDate is returned by Storage.CreateSnapshot when a requested
|
||||
// index is older than the existing snapshot.
|
||||
var ErrSnapOutOfDate = errors.New("requested index is older than the existing snapshot")
|
||||
|
||||
var ErrUnavailable = errors.New("requested entry at index is unavailable")
|
||||
|
||||
// Storage is an interface that may be implemented by the application
|
||||
// to retrieve log entries from storage.
|
||||
//
|
||||
// If any Storage method returns an error, the raft instance will
|
||||
// become inoperable and refuse to participate in elections; the
|
||||
// application is responsible for cleanup and recovery in this case.
|
||||
type Storage interface {
|
||||
// InitialState returns the saved HardState and ConfState information.
|
||||
InitialState() (pb.HardState, pb.ConfState, error)
|
||||
// Entries returns a slice of log entries in the range [lo,hi).
|
||||
// MaxSize limits the total size of the log entries returned, but
|
||||
// Entries returns at least one entry if any.
|
||||
Entries(lo, hi, maxSize uint64) ([]pb.Entry, error)
|
||||
// Term returns the term of entry i, which must be in the range
|
||||
// [FirstIndex()-1, LastIndex()]. The term of the entry before
|
||||
// FirstIndex is retained for matching purposes even though the
|
||||
// rest of that entry may not be available.
|
||||
Term(i uint64) (uint64, error)
|
||||
// LastIndex returns the index of the last entry in the log.
|
||||
LastIndex() (uint64, error)
|
||||
// FirstIndex returns the index of the first log entry that is
|
||||
// possibly available via Entries (older entries have been incorporated
|
||||
// into the latest Snapshot; if storage only contains the dummy entry the
|
||||
// first log entry is not available).
|
||||
FirstIndex() (uint64, error)
|
||||
// Snapshot returns the most recent snapshot.
|
||||
// If snapshot is temporarily unavailable, it should return ErrSnapshotTemporarilyUnavailable,
|
||||
// so raft state machine could know that Storage needs some time to prepare
|
||||
// snapshot and call Snapshot later.
|
||||
Snapshot() (pb.Snapshot, error)
|
||||
}
|
||||
|
||||
// MemoryStorage implements the Storage interface backed by an
|
||||
// in-memory array.
|
||||
type MemoryStorage struct {
|
||||
// Protects access to all fields. Most methods of MemoryStorage are
|
||||
// run on the raft goroutine, but Append() is run on an application
|
||||
// goroutine.
|
||||
sync.Mutex
|
||||
|
||||
hardState pb.HardState
|
||||
snapshot pb.Snapshot
|
||||
// ents[i] has raft log position i+snapshot.Metadata.Index
|
||||
ents []pb.Entry
|
||||
}
|
||||
|
||||
// NewMemoryStorage creates an empty MemoryStorage.
|
||||
func NewMemoryStorage() *MemoryStorage {
|
||||
return &MemoryStorage{
|
||||
// When starting from scratch populate the list with a dummy entry at term zero.
|
||||
ents: make([]pb.Entry, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// InitialState implements the Storage interface.
|
||||
func (ms *MemoryStorage) InitialState() (pb.HardState, pb.ConfState, error) {
|
||||
return ms.hardState, ms.snapshot.Metadata.ConfState, nil
|
||||
}
|
||||
|
||||
// SetHardState saves the current HardState.
|
||||
func (ms *MemoryStorage) SetHardState(st pb.HardState) error {
|
||||
ms.hardState = st
|
||||
return nil
|
||||
}
|
||||
|
||||
// Entries implements the Storage interface.
|
||||
func (ms *MemoryStorage) Entries(lo, hi, maxSize uint64) ([]pb.Entry, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
offset := ms.ents[0].Index
|
||||
if lo <= offset {
|
||||
return nil, ErrCompacted
|
||||
}
|
||||
if hi > ms.lastIndex()+1 {
|
||||
raftLogger.Panicf("entries' hi(%d) is out of bound lastindex(%d)", hi, ms.lastIndex())
|
||||
}
|
||||
// only contains dummy entries.
|
||||
if len(ms.ents) == 1 {
|
||||
return nil, ErrUnavailable
|
||||
}
|
||||
|
||||
ents := ms.ents[lo-offset : hi-offset]
|
||||
return limitSize(ents, maxSize), nil
|
||||
}
|
||||
|
||||
// Term implements the Storage interface.
|
||||
func (ms *MemoryStorage) Term(i uint64) (uint64, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
offset := ms.ents[0].Index
|
||||
if i < offset {
|
||||
return 0, ErrCompacted
|
||||
}
|
||||
return ms.ents[i-offset].Term, nil
|
||||
}
|
||||
|
||||
// LastIndex implements the Storage interface.
|
||||
func (ms *MemoryStorage) LastIndex() (uint64, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
return ms.lastIndex(), nil
|
||||
}
|
||||
|
||||
func (ms *MemoryStorage) lastIndex() uint64 {
|
||||
return ms.ents[0].Index + uint64(len(ms.ents)) - 1
|
||||
}
|
||||
|
||||
// FirstIndex implements the Storage interface.
|
||||
func (ms *MemoryStorage) FirstIndex() (uint64, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
return ms.firstIndex(), nil
|
||||
}
|
||||
|
||||
func (ms *MemoryStorage) firstIndex() uint64 {
|
||||
return ms.ents[0].Index + 1
|
||||
}
|
||||
|
||||
// Snapshot implements the Storage interface.
|
||||
func (ms *MemoryStorage) Snapshot() (pb.Snapshot, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
return ms.snapshot, nil
|
||||
}
|
||||
|
||||
// ApplySnapshot overwrites the contents of this Storage object with
|
||||
// those of the given snapshot.
|
||||
func (ms *MemoryStorage) ApplySnapshot(snap pb.Snapshot) error {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
|
||||
// TODO: return ErrSnapOutOfDate?
|
||||
ms.snapshot = snap
|
||||
ms.ents = []pb.Entry{{Term: snap.Metadata.Term, Index: snap.Metadata.Index}}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSnapshot makes a snapshot which can be retrieved with Snapshot() and
|
||||
// can be used to reconstruct the state at that point.
|
||||
// If any configuration changes have been made since the last compaction,
|
||||
// the result of the last ApplyConfChange must be passed in.
|
||||
func (ms *MemoryStorage) CreateSnapshot(i uint64, cs *pb.ConfState, data []byte) (pb.Snapshot, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
if i <= ms.snapshot.Metadata.Index {
|
||||
return pb.Snapshot{}, ErrSnapOutOfDate
|
||||
}
|
||||
|
||||
offset := ms.ents[0].Index
|
||||
if i > ms.lastIndex() {
|
||||
raftLogger.Panicf("snapshot %d is out of bound lastindex(%d)", i, ms.lastIndex())
|
||||
}
|
||||
|
||||
ms.snapshot.Metadata.Index = i
|
||||
ms.snapshot.Metadata.Term = ms.ents[i-offset].Term
|
||||
if cs != nil {
|
||||
ms.snapshot.Metadata.ConfState = *cs
|
||||
}
|
||||
ms.snapshot.Data = data
|
||||
return ms.snapshot, nil
|
||||
}
|
||||
|
||||
// Compact discards all log entries prior to compactIndex.
|
||||
// It is the application's responsibility to not attempt to compact an index
|
||||
// greater than raftLog.applied.
|
||||
func (ms *MemoryStorage) Compact(compactIndex uint64) error {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
offset := ms.ents[0].Index
|
||||
if compactIndex <= offset {
|
||||
return ErrCompacted
|
||||
}
|
||||
if compactIndex > ms.lastIndex() {
|
||||
raftLogger.Panicf("compact %d is out of bound lastindex(%d)", compactIndex, ms.lastIndex())
|
||||
}
|
||||
|
||||
i := compactIndex - offset
|
||||
ents := make([]pb.Entry, 1, 1+uint64(len(ms.ents))-i)
|
||||
ents[0].Index = ms.ents[i].Index
|
||||
ents[0].Term = ms.ents[i].Term
|
||||
ents = append(ents, ms.ents[i+1:]...)
|
||||
ms.ents = ents
|
||||
return nil
|
||||
}
|
||||
|
||||
// Append the new entries to storage.
|
||||
// TODO (xiangli): ensure the entries are continuous and
|
||||
// entries[0].Index > ms.entries[0].Index
|
||||
func (ms *MemoryStorage) Append(entries []pb.Entry) error {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
if len(entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
first := ms.ents[0].Index + 1
|
||||
last := entries[0].Index + uint64(len(entries)) - 1
|
||||
|
||||
// shortcut if there is no new entry.
|
||||
if last < first {
|
||||
return nil
|
||||
}
|
||||
// truncate compacted entries
|
||||
if first > entries[0].Index {
|
||||
entries = entries[first-entries[0].Index:]
|
||||
}
|
||||
|
||||
offset := entries[0].Index - ms.ents[0].Index
|
||||
switch {
|
||||
case uint64(len(ms.ents)) > offset:
|
||||
ms.ents = append([]pb.Entry{}, ms.ents[:offset]...)
|
||||
ms.ents = append(ms.ents, entries...)
|
||||
case uint64(len(ms.ents)) == offset:
|
||||
ms.ents = append(ms.ents, entries...)
|
||||
default:
|
||||
raftLogger.Panicf("missing log entry [last: %d, append at: %d]",
|
||||
ms.lastIndex(), entries[0].Index)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
116
vendor/github.com/coreos/etcd/raft/util.go
generated
vendored
Normal file
116
vendor/github.com/coreos/etcd/raft/util.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raft
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
func (st StateType) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("%q", st.String())), nil
|
||||
}
|
||||
|
||||
// uint64Slice implements sort interface
|
||||
type uint64Slice []uint64
|
||||
|
||||
func (p uint64Slice) Len() int { return len(p) }
|
||||
func (p uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
|
||||
func (p uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
func min(a, b uint64) uint64 {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func max(a, b uint64) uint64 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func IsLocalMsg(m pb.Message) bool {
|
||||
return m.Type == pb.MsgHup || m.Type == pb.MsgBeat || m.Type == pb.MsgUnreachable || m.Type == pb.MsgSnapStatus || m.Type == pb.MsgCheckQuorum
|
||||
}
|
||||
|
||||
func IsResponseMsg(m pb.Message) bool {
|
||||
return m.Type == pb.MsgAppResp || m.Type == pb.MsgVoteResp || m.Type == pb.MsgHeartbeatResp || m.Type == pb.MsgUnreachable
|
||||
}
|
||||
|
||||
// EntryFormatter can be implemented by the application to provide human-readable formatting
|
||||
// of entry data. Nil is a valid EntryFormatter and will use a default format.
|
||||
type EntryFormatter func([]byte) string
|
||||
|
||||
// DescribeMessage returns a concise human-readable description of a
|
||||
// Message for debugging.
|
||||
func DescribeMessage(m pb.Message, f EntryFormatter) string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%x->%x %v Term:%d Log:%d/%d", m.From, m.To, m.Type, m.Term, m.LogTerm, m.Index)
|
||||
if m.Reject {
|
||||
fmt.Fprintf(&buf, " Rejected")
|
||||
if m.RejectHint != 0 {
|
||||
fmt.Fprintf(&buf, "(Hint:%d)", m.RejectHint)
|
||||
}
|
||||
}
|
||||
if m.Commit != 0 {
|
||||
fmt.Fprintf(&buf, " Commit:%d", m.Commit)
|
||||
}
|
||||
if len(m.Entries) > 0 {
|
||||
fmt.Fprintf(&buf, " Entries:[")
|
||||
for i, e := range m.Entries {
|
||||
if i != 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(DescribeEntry(e, f))
|
||||
}
|
||||
fmt.Fprintf(&buf, "]")
|
||||
}
|
||||
if !IsEmptySnap(m.Snapshot) {
|
||||
fmt.Fprintf(&buf, " Snapshot:%v", m.Snapshot)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// DescribeEntry returns a concise human-readable description of an
|
||||
// Entry for debugging.
|
||||
func DescribeEntry(e pb.Entry, f EntryFormatter) string {
|
||||
var formatted string
|
||||
if e.Type == pb.EntryNormal && f != nil {
|
||||
formatted = f(e.Data)
|
||||
} else {
|
||||
formatted = fmt.Sprintf("%q", e.Data)
|
||||
}
|
||||
return fmt.Sprintf("%d/%d %s %s", e.Term, e.Index, e.Type, formatted)
|
||||
}
|
||||
|
||||
func limitSize(ents []pb.Entry, maxSize uint64) []pb.Entry {
|
||||
if len(ents) == 0 {
|
||||
return ents
|
||||
}
|
||||
size := ents[0].Size()
|
||||
var limit int
|
||||
for limit = 1; limit < len(ents); limit++ {
|
||||
size += ents[limit].Size()
|
||||
if uint64(size) > maxSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ents[:limit]
|
||||
}
|
||||
Reference in New Issue
Block a user