forked from lei.chen/myxce
feat(raft-commit-safety): (lastLogTerm,lastLogIndex) vote+prevote freshness (tasks 1.3, 4.1-4.2, 5.1-5.2, 6.2)
logOk (raft.tla:285, closes G6): local_last_log_term = log_len>0 ? on_get_entry_term(log_len):0; logOk = cand_last_log_term > local || (== && cand_last_log_index >= log_len). Lexicographic: term dominates, tie decided by index. Shared raft_log_ok helper. Vote (4.2): logOk deny inserted after stale-term/UpdateTerm/role-gate/one-vote-per-term and BEFORE on_vote_eligible (denies even when the app gate would grant); persist-before-grant intact. PreVote (5.2): identical logOk, fully side-effect-free (no term/vote/role/commit mutation, no on_persist). Signatures extended (1.3) on raft_on_vote_request/pre_vote_request; all 39 lib call sites + sim routing updated; sim captures the candidate's send-time last-log tuple (raft_sim_rpc_t). Empty-log call sites pass (0,0) → logOk true → existing outcomes preserved. 9 freshness tests. NOTE: controller cluster.c vote callers still use the old signatures and will NOT compile — an accepted, design-documented revalidation trigger; the controller adapter plumbing is owned by the downstream ha-term-safety spec (to be implemented next). Library suite ASAN green (239 tests).
This commit is contained in:
parent
195f660f9a
commit
56485e2fa3
5 changed files with 449 additions and 62 deletions
|
|
@ -23,7 +23,7 @@
|
||||||
- _Boundary: raft.h_
|
- _Boundary: raft.h_
|
||||||
- _Depends: 1.1_
|
- _Depends: 1.1_
|
||||||
|
|
||||||
- [ ] 1.3 Declare `raft_advance_commit` and extend the vote/PreVote signatures with last_log_term/last_log_index
|
- [x] 1.3 Declare `raft_advance_commit` and extend the vote/PreVote signatures with last_log_term/last_log_index
|
||||||
- Declare `int raft_advance_commit(raft_state_t *s)`
|
- Declare `int raft_advance_commit(raft_state_t *s)`
|
||||||
- Extend `raft_on_vote_request` and `raft_on_pre_vote_request` to accept `cand_last_log_term` and `cand_last_log_index` (the chosen "library owns logOk" plumbing — Option B in design.md)
|
- Extend `raft_on_vote_request` and `raft_on_pre_vote_request` to accept `cand_last_log_term` and `cand_last_log_index` (the chosen "library owns logOk" plumbing — Option B in design.md)
|
||||||
- Update the header doc comments to describe the new logOk gate and the commit-advance action
|
- Update the header doc comments to describe the new logOk gate and the commit-advance action
|
||||||
|
|
@ -74,7 +74,7 @@
|
||||||
|
|
||||||
## 4. (lastLogTerm, lastLogIndex) vote freshness (raft.tla:285, closes G6)
|
## 4. (lastLogTerm, lastLogIndex) vote freshness (raft.tla:285, closes G6)
|
||||||
|
|
||||||
- [ ] 4.1 RED: binding-vote freshness tests
|
- [x] 4.1 RED: binding-vote freshness tests
|
||||||
- Test: candidate with stale `last_log_term` denied even when `last_log_index` is greater; candidate with fresher `last_log_term` granted with a smaller `last_log_index`; tie on term grants only when `cand_last_log_index >= log_len`; the voter's own last-log term/index come from `on_get_entry_term(log_len)`/`log_len` (term 0 when empty)
|
- Test: candidate with stale `last_log_term` denied even when `last_log_index` is greater; candidate with fresher `last_log_term` granted with a smaller `last_log_index`; tie on term grants only when `cand_last_log_index >= log_len`; the voter's own last-log term/index come from `on_get_entry_term(log_len)`/`log_len` (term 0 when empty)
|
||||||
- Test: `!logOk` denies even when `on_vote_eligible` would grant (logOk applied BEFORE the application gate); persist-before-grant unchanged on the grant path
|
- Test: `!logOk` denies even when `on_vote_eligible` would grant (logOk applied BEFORE the application gate); persist-before-grant unchanged on the grant path
|
||||||
- Observable completion: tests compile and FAIL
|
- Observable completion: tests compile and FAIL
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
- _Boundary: test_raft.c_
|
- _Boundary: test_raft.c_
|
||||||
- _Depends: 1.3_
|
- _Depends: 1.3_
|
||||||
|
|
||||||
- [ ] 4.2 GREEN: implement the logOk gate in `raft_on_vote_request`
|
- [x] 4.2 GREEN: implement the logOk gate in `raft_on_vote_request`
|
||||||
- Compute `local_last_log_term = (log_len > 0) ? on_get_entry_term(log_len) : 0`; `logOk = (cand_last_log_term > local_last_log_term) || (cand_last_log_term == local_last_log_term && cand_last_log_index >= log_len)`
|
- Compute `local_last_log_term = (log_len > 0) ? on_get_entry_term(log_len) : 0`; `logOk = (cand_last_log_term > local_last_log_term) || (cand_last_log_term == local_last_log_term && cand_last_log_index >= log_len)`
|
||||||
- Insert the logOk deny BEFORE `on_vote_eligible`, after the existing stale-term/UpdateTerm/one-vote-per-term steps; leave persist-before-grant and all existing invariants in place
|
- Insert the logOk deny BEFORE `on_vote_eligible`, after the existing stale-term/UpdateTerm/one-vote-per-term steps; leave persist-before-grant and all existing invariants in place
|
||||||
- Observable completion: 4.1 tests pass; no regression in existing vote tests; the logOk block is clearly before `on_vote_eligible` in `raft.c`
|
- Observable completion: 4.1 tests pass; no regression in existing vote tests; the logOk block is clearly before `on_vote_eligible` in `raft.c`
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
|
|
||||||
## 5. (lastLogTerm, lastLogIndex) PreVote freshness (raft.tla:285, side-effect-free)
|
## 5. (lastLogTerm, lastLogIndex) PreVote freshness (raft.tla:285, side-effect-free)
|
||||||
|
|
||||||
- [ ] 5.1 RED: PreVote freshness tests (identical rule, no side effects)
|
- [x] 5.1 RED: PreVote freshness tests (identical rule, no side effects)
|
||||||
- Test: `raft_on_pre_vote_request` applies the SAME `(lastLogTerm, lastLogIndex)` logOk as the binding vote; a stale tuple is denied; the existing stale-term deny and `on_vote_eligible` gate remain in force
|
- Test: `raft_on_pre_vote_request` applies the SAME `(lastLogTerm, lastLogIndex)` logOk as the binding vote; a stale tuple is denied; the existing stale-term deny and `on_vote_eligible` gate remain in force
|
||||||
- Test: across grant and deny, no mutation of `term`/`voted_for`/`voted_for_term`/`role`/`commit_index` and no `on_persist` call (PreVote invariant)
|
- Test: across grant and deny, no mutation of `term`/`voted_for`/`voted_for_term`/`role`/`commit_index` and no `on_persist` call (PreVote invariant)
|
||||||
- Observable completion: tests compile and FAIL
|
- Observable completion: tests compile and FAIL
|
||||||
|
|
@ -100,7 +100,7 @@
|
||||||
- _Boundary: test_raft.c_
|
- _Boundary: test_raft.c_
|
||||||
- _Depends: 1.3_
|
- _Depends: 1.3_
|
||||||
|
|
||||||
- [ ] 5.2 GREEN: implement the logOk gate in `raft_on_pre_vote_request`
|
- [x] 5.2 GREEN: implement the logOk gate in `raft_on_pre_vote_request`
|
||||||
- Apply the identical logOk predicate (factored to match 4.2) before/with the existing eligibility gate; mutate nothing; no `on_persist`
|
- Apply the identical logOk predicate (factored to match 4.2) before/with the existing eligibility gate; mutate nothing; no `on_persist`
|
||||||
- Observable completion: 5.1 tests pass; no regression in existing PreVote tests; the predicate matches the binding-vote path exactly
|
- Observable completion: 5.1 tests pass; no regression in existing PreVote tests; the predicate matches the binding-vote path exactly
|
||||||
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
|
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
|
||||||
|
|
@ -118,7 +118,7 @@
|
||||||
- _Boundary: raft_sim.c_
|
- _Boundary: raft_sim.c_
|
||||||
- _Depends: 3.2_
|
- _Depends: 3.2_
|
||||||
|
|
||||||
- [ ] 6.2 Plumb `(last_log_term, last_log_index)` through the sim vote/PreVote routing
|
- [x] 6.2 Plumb `(last_log_term, last_log_index)` through the sim vote/PreVote routing
|
||||||
- Update the sim's vote and PreVote delivery to read the candidate's last-log `(term, index)` from its in-memory log (`term at log_len` / `log_len`) and pass them to the extended `raft_on_vote_request` / `raft_on_pre_vote_request`
|
- Update the sim's vote and PreVote delivery to read the candidate's last-log `(term, index)` from its in-memory log (`term at log_len` / `log_len`) and pass them to the extended `raft_on_vote_request` / `raft_on_pre_vote_request`
|
||||||
- Observable completion: existing election sim tests still pass with the extended signatures; a node with a stale last-log term is denied a vote it would previously have won on LSN alone
|
- Observable completion: existing election sim tests still pass with the extended signatures; a node with a stale last-log term is denied a vote it would previously have won on LSN alone
|
||||||
- _Requirements: 6.5, 3.2, 4.2_
|
- _Requirements: 6.5, 3.2, 4.2_
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,25 @@ int raft_start_election(raft_state_t *s)
|
||||||
return RAFT_OK;
|
return RAFT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (lastLogTerm, lastLogIndex) freshness predicate (raft.tla
|
||||||
|
* HandleRequestVoteRequest logOk, line 285, closes G6). The voter's own last-log
|
||||||
|
* tuple is (on_get_entry_term(log_len), log_len) — term 0 when the log is empty,
|
||||||
|
* matching the on_get_entry_term 0-means-absent sentinel. The library owns this
|
||||||
|
* decision (design "Option B: library owns logOk") and applies it identically on
|
||||||
|
* the binding vote and the PreVote eligibility paths. */
|
||||||
|
static int raft_log_ok(const raft_state_t *s, uint64_t cand_last_log_term,
|
||||||
|
uint64_t cand_last_log_index)
|
||||||
|
{
|
||||||
|
uint64_t local_last_log_term =
|
||||||
|
(s->log_len > 0) ? s->cbs->on_get_entry_term(s->ctx, s->log_len) : 0;
|
||||||
|
return (cand_last_log_term > local_last_log_term) ||
|
||||||
|
(cand_last_log_term == local_last_log_term &&
|
||||||
|
cand_last_log_index >= s->log_len);
|
||||||
|
}
|
||||||
|
|
||||||
int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term,
|
int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term,
|
||||||
|
uint64_t cand_last_log_term,
|
||||||
|
uint64_t cand_last_log_index,
|
||||||
uint64_t *reply_term)
|
uint64_t *reply_term)
|
||||||
{
|
{
|
||||||
/* Step 1: stale term — deny immediately, no state change */
|
/* Step 1: stale term — deny immediately, no state change */
|
||||||
|
|
@ -208,7 +226,18 @@ int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term,
|
||||||
/* same candidate — fall through to re-persist and re-grant */
|
/* same candidate — fall through to re-persist and re-grant */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Step 5: application-level policy gate (freshness, log lsn, etc.) */
|
/* Step 5: (lastLogTerm, lastLogIndex) freshness gate (raft.tla
|
||||||
|
* HandleRequestVoteRequest logOk, 285, closes G6). This precedes the
|
||||||
|
* application gate: a !logOk denies regardless of on_vote_eligible (Req 3.4),
|
||||||
|
* without weakening the stale-term / one-vote-per-term / persist-before-grant
|
||||||
|
* invariants above (Req 3.5). */
|
||||||
|
if (!raft_log_ok(s, cand_last_log_term, cand_last_log_index)) {
|
||||||
|
if (s->cbs->on_trace)
|
||||||
|
s->cbs->on_trace(s->ctx, "vote_denied_log_stale", NULL);
|
||||||
|
return RAFT_DENY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Step 6: application-level policy gate (freshness, log lsn, etc.) */
|
||||||
if (s->cbs->on_vote_eligible != NULL) {
|
if (s->cbs->on_vote_eligible != NULL) {
|
||||||
if (s->cbs->on_vote_eligible(s->ctx, from_idx, term) == 0) {
|
if (s->cbs->on_vote_eligible(s->ctx, from_idx, term) == 0) {
|
||||||
if (s->cbs->on_trace)
|
if (s->cbs->on_trace)
|
||||||
|
|
@ -217,7 +246,7 @@ int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Step 6: persist before granting — durability guarantee */
|
/* Step 7: persist before granting — durability guarantee */
|
||||||
if (s->cbs->on_persist(s->ctx, term, from_idx) != 0) {
|
if (s->cbs->on_persist(s->ctx, term, from_idx) != 0) {
|
||||||
/* Persist failed — deny without updating voted_for */
|
/* Persist failed — deny without updating voted_for */
|
||||||
return RAFT_DENY;
|
return RAFT_DENY;
|
||||||
|
|
@ -231,15 +260,18 @@ int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term,
|
||||||
return RAFT_GRANT;
|
return RAFT_GRANT;
|
||||||
}
|
}
|
||||||
|
|
||||||
int raft_on_pre_vote_request(raft_state_t *s, int from_idx, uint64_t cand_term)
|
int raft_on_pre_vote_request(raft_state_t *s, int from_idx, uint64_t cand_term,
|
||||||
|
uint64_t cand_last_log_term,
|
||||||
|
uint64_t cand_last_log_index)
|
||||||
{
|
{
|
||||||
/* PreVote is a side-effect-free straw poll: it answers "would you vote for
|
/* PreVote is a side-effect-free straw poll: it answers "would you vote for
|
||||||
* me in cand_term?" WITHOUT changing any persistent state. Unlike
|
* me in cand_term?" WITHOUT changing any persistent state. Unlike
|
||||||
* raft_on_vote_request, this function never adopts cand_term (even when
|
* raft_on_vote_request, this function never adopts cand_term (even when
|
||||||
* higher — that adoption is the very thing PreVote exists to prevent), never
|
* higher — that adoption is the very thing PreVote exists to prevent), never
|
||||||
* touches term / voted_for / voted_for_term / role, and never calls
|
* touches term / voted_for / voted_for_term / role, and never calls
|
||||||
* on_persist. The dispatcher applies the freshness floor before this call,
|
* on_persist. The library applies the SAME (lastLogTerm, lastLogIndex)
|
||||||
* exactly as on the real-vote path. */
|
* logOk freshness gate as the real-vote path (via raft_log_ok), so a stale
|
||||||
|
* candidate is rejected here exactly as it would be in a binding vote. */
|
||||||
|
|
||||||
/* Step 1: stale term — deny (mirrors raft_on_vote_request Step 1). A
|
/* Step 1: stale term — deny (mirrors raft_on_vote_request Step 1). A
|
||||||
* candidate proposing a term we already passed could never win a real
|
* candidate proposing a term we already passed could never win a real
|
||||||
|
|
@ -251,10 +283,21 @@ int raft_on_pre_vote_request(raft_state_t *s, int from_idx, uint64_t cand_term)
|
||||||
return RAFT_DENY;
|
return RAFT_DENY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Step 2: application-level eligibility gate — reuse the EXISTING
|
/* Step 2: (lastLogTerm, lastLogIndex) freshness gate — the IDENTICAL logOk
|
||||||
|
* as the binding vote path (raft.tla 285, closes G6 for PreVote, Req 4.1).
|
||||||
|
* The library owns this predicate (the dispatcher no longer applies freshness
|
||||||
|
* before this call). Side-effect-free: a deny mutates nothing and never
|
||||||
|
* persists (Req 4.4). */
|
||||||
|
if (!raft_log_ok(s, cand_last_log_term, cand_last_log_index)) {
|
||||||
|
if (s->cbs->on_trace)
|
||||||
|
s->cbs->on_trace(s->ctx, "pre_vote_denied_log_stale", NULL);
|
||||||
|
return RAFT_DENY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Step 3: application-level eligibility gate — reuse the EXISTING
|
||||||
* on_vote_eligible callback (primary-deny / fresh-heartbeat stickiness /
|
* on_vote_eligible callback (primary-deny / fresh-heartbeat stickiness /
|
||||||
* catch-up deny), identical to raft_on_vote_request Step 5. If NULL, every
|
* catch-up deny), identical to raft_on_vote_request's application gate. If
|
||||||
* Raft-eligible candidate is granted. */
|
* NULL, every Raft-eligible candidate is granted. */
|
||||||
if (s->cbs->on_vote_eligible != NULL) {
|
if (s->cbs->on_vote_eligible != NULL) {
|
||||||
if (s->cbs->on_vote_eligible(s->ctx, from_idx, cand_term) == 0) {
|
if (s->cbs->on_vote_eligible(s->ctx, from_idx, cand_term) == 0) {
|
||||||
if (s->cbs->on_trace)
|
if (s->cbs->on_trace)
|
||||||
|
|
|
||||||
|
|
@ -244,12 +244,29 @@ int raft_start_election(raft_state_t *s);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* raft_on_vote_request: process an incoming RequestVote RPC.
|
* raft_on_vote_request: process an incoming RequestVote RPC.
|
||||||
* Applies Raft invariants (term comparison, one-vote-per-term).
|
* Applies Raft invariants (term comparison, one-vote-per-term), then the
|
||||||
* If invariants pass, calls on_vote_eligible to allow application policy.
|
* (lastLogTerm, lastLogIndex) freshness gate (raft.tla HandleRequestVoteRequest
|
||||||
|
* logOk, line 285, closes G6), then the application policy gate.
|
||||||
|
*
|
||||||
|
* The library owns the logOk decision (design "Option B: library owns logOk"):
|
||||||
|
* local_last_log_term = (log_len > 0) ? on_get_entry_term(log_len) : 0
|
||||||
|
* logOk = (cand_last_log_term > local_last_log_term)
|
||||||
|
* || (cand_last_log_term == local_last_log_term
|
||||||
|
* && cand_last_log_index >= log_len)
|
||||||
|
* The candidate advertises (cand_last_log_term, cand_last_log_index) in its wire
|
||||||
|
* RequestVote; the caller decodes and passes them here.
|
||||||
|
*
|
||||||
|
* Step order: stale-term deny → UpdateTerm/step-down → role gate →
|
||||||
|
* one-vote-per-term → logOk gate (NEW; precedes the application gate, so a
|
||||||
|
* !logOk denies regardless of on_vote_eligible) → on_vote_eligible →
|
||||||
|
* persist-before-grant.
|
||||||
|
*
|
||||||
* Fires on_persist before granting.
|
* Fires on_persist before granting.
|
||||||
* Returns RAFT_GRANT (1) or RAFT_DENY (0); sets *reply_term to current term.
|
* Returns RAFT_GRANT (1) or RAFT_DENY (0); sets *reply_term to current term.
|
||||||
*/
|
*/
|
||||||
int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term,
|
int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term,
|
||||||
|
uint64_t cand_last_log_term,
|
||||||
|
uint64_t cand_last_log_index,
|
||||||
uint64_t *reply_term);
|
uint64_t *reply_term);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -301,13 +318,17 @@ int raft_begin_pre_vote(raft_state_t *s);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* raft_on_pre_vote_request: answer an incoming PreVote probe.
|
* raft_on_pre_vote_request: answer an incoming PreVote probe.
|
||||||
* Denies if cand_term < s->term; otherwise returns on_vote_eligible(ctx,
|
* Denies if cand_term < s->term; then applies the IDENTICAL (lastLogTerm,
|
||||||
* from_idx, cand_term) (granting when the gate is NULL). Never touches term,
|
* lastLogIndex) logOk as raft_on_vote_request (the library owns the predicate —
|
||||||
* voted_for, role, or fires on_persist. Freshness is applied by the
|
* it is NO LONGER applied by the dispatcher before this call), then consults
|
||||||
* dispatcher before this call (same contract as the real vote path).
|
* on_vote_eligible (granting when the gate is NULL). Mutates NOTHING — never
|
||||||
|
* touches term, voted_for, voted_for_term, role, or commit_index, and never
|
||||||
|
* fires on_persist — preserving the PreVote side-effect-free invariant.
|
||||||
* Returns 1 = grant, 0 = deny.
|
* Returns 1 = grant, 0 = deny.
|
||||||
*/
|
*/
|
||||||
int raft_on_pre_vote_request(raft_state_t *s, int from_idx, uint64_t cand_term);
|
int raft_on_pre_vote_request(raft_state_t *s, int from_idx, uint64_t cand_term,
|
||||||
|
uint64_t cand_last_log_term,
|
||||||
|
uint64_t cand_last_log_index);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* raft_on_pre_vote_reply: tally a reply to our PreVote.
|
* raft_on_pre_vote_reply: tally a reply to our PreVote.
|
||||||
|
|
@ -423,11 +444,17 @@ void raft_snapshot(const raft_state_t *s, raft_snapshot_t *snap);
|
||||||
/* Upper bound on buffered AppendEntries awaiting delivery (mirrors pending[]). */
|
/* Upper bound on buffered AppendEntries awaiting delivery (mirrors pending[]). */
|
||||||
#define RAFT_SIM_MAX_AE 64
|
#define RAFT_SIM_MAX_AE 64
|
||||||
|
|
||||||
/* Buffered RPC entry: one pending vote-request to route */
|
/* Buffered RPC entry: one pending vote-request to route.
|
||||||
|
* last_log_term/last_log_index capture the CANDIDATE's advertised last-log tuple
|
||||||
|
* at SEND time (mirroring raft_sim_ae_t.leader_term) so the (lastLogTerm,
|
||||||
|
* lastLogIndex) freshness gate (raft-commit-safety) is evaluated against the
|
||||||
|
* candidate's send-epoch log, not a value re-read at delivery. */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int src;
|
int src;
|
||||||
int dst;
|
int dst;
|
||||||
uint64_t term;
|
uint64_t term;
|
||||||
|
uint64_t last_log_term; /* candidate's last-log term at send (0 if empty) */
|
||||||
|
uint64_t last_log_index; /* candidate's last-log index at send (== log_count) */
|
||||||
} raft_sim_rpc_t;
|
} raft_sim_rpc_t;
|
||||||
|
|
||||||
/* One in-memory log entry: term stamp + fixed-size payload bytes.
|
/* One in-memory log entry: term stamp + fixed-size payload bytes.
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,14 @@ static int sim_send_vote_request(void *ctx, int peer_idx, uint64_t term)
|
||||||
rpc->src = node->idx;
|
rpc->src = node->idx;
|
||||||
rpc->dst = peer_idx;
|
rpc->dst = peer_idx;
|
||||||
rpc->term = term;
|
rpc->term = term;
|
||||||
|
/* Capture the CANDIDATE's last-log tuple at SEND time from its own in-memory
|
||||||
|
* log (the library holds no entry bytes): last_log_index == log_count,
|
||||||
|
* last_log_term == term of the tail entry (0 when the log is empty). This
|
||||||
|
* feeds the (lastLogTerm, lastLogIndex) freshness gate at delivery. */
|
||||||
|
rpc->last_log_index = (uint64_t)node->log_count;
|
||||||
|
rpc->last_log_term = (node->log_count > 0)
|
||||||
|
? node->log[node->log_count - 1].term
|
||||||
|
: 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,9 +327,12 @@ void raft_sim_deliver_pending(raft_sim_cluster_t *c)
|
||||||
if (c->nodes[src].partition || c->nodes[dst].partition)
|
if (c->nodes[src].partition || c->nodes[dst].partition)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Deliver vote request to dst */
|
/* Deliver vote request to dst, passing the candidate's send-time last-log
|
||||||
|
* tuple so dst evaluates the (lastLogTerm, lastLogIndex) freshness gate. */
|
||||||
uint64_t reply_term = 0;
|
uint64_t reply_term = 0;
|
||||||
int granted = raft_on_vote_request(&c->nodes[dst].state, src, term,
|
int granted = raft_on_vote_request(&c->nodes[dst].state, src, term,
|
||||||
|
rpc->last_log_term,
|
||||||
|
rpc->last_log_index,
|
||||||
&reply_term);
|
&reply_term);
|
||||||
|
|
||||||
/* Route reply back to src */
|
/* Route reply back to src */
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,13 @@ typedef struct {
|
||||||
int vote_eligible_return_value; /* configurable: 1=grant, 0=deny; default 1 */
|
int vote_eligible_return_value; /* configurable: 1=grant, 0=deny; default 1 */
|
||||||
int vote_eligible_call_count;
|
int vote_eligible_call_count;
|
||||||
|
|
||||||
|
/* on_get_entry_term mock (raft-commit-safety freshness, task 4.1/5.1).
|
||||||
|
* The voter's local last-log term is returned for index == get_entry_term_log_len;
|
||||||
|
* any other index returns 0 (absent). Default 0 keeps empty-log scenarios at the
|
||||||
|
* pre-existing local_last_log_term == 0. */
|
||||||
|
uint64_t local_last_log_term; /* term reported at index == get_entry_term_log_len */
|
||||||
|
uint64_t get_entry_term_log_len; /* the index treated as the local last log */
|
||||||
|
|
||||||
/* on_trace mock */
|
/* on_trace mock */
|
||||||
int trace_call_count;
|
int trace_call_count;
|
||||||
char last_trace_event[64];
|
char last_trace_event[64];
|
||||||
|
|
@ -241,6 +248,19 @@ static int mock_vote_eligible(void *ctx, int from_idx, uint64_t term)
|
||||||
return g_mock.vote_eligible_return_value;
|
return g_mock.vote_eligible_return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Configurable local-log term reporter for freshness tests (task 4.1/5.1).
|
||||||
|
* Returns g_mock.local_last_log_term for the configured last-log index, 0
|
||||||
|
* otherwise. Defaults (both 0) reproduce the previous always-0 behaviour, so
|
||||||
|
* empty-log call sites are unaffected. */
|
||||||
|
static uint64_t mock_get_entry_term(void *ctx, uint64_t index)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
if (g_mock.get_entry_term_log_len != 0 &&
|
||||||
|
index == g_mock.get_entry_term_log_len)
|
||||||
|
return g_mock.local_last_log_term;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void mock_trace(void *ctx, const char *event, const char *detail)
|
static void mock_trace(void *ctx, const char *event, const char *detail)
|
||||||
{
|
{
|
||||||
(void)ctx; (void)detail;
|
(void)ctx; (void)detail;
|
||||||
|
|
@ -940,7 +960,7 @@ static void test_vote_req_stale_term(void)
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, s.role);
|
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, s.role);
|
||||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 3, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 3, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||||
|
|
@ -981,7 +1001,7 @@ static void test_vote_req_higher_term_updates_state(void)
|
||||||
s.voted_for = 0; /* voted for self */
|
s.voted_for = 0; /* voted for self */
|
||||||
s.voted_for_term = 2;
|
s.voted_for_term = 2;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||||
|
|
||||||
/* Term upgrade must happen: term updated, role stepped down */
|
/* Term upgrade must happen: term updated, role stepped down */
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||||
|
|
@ -1021,7 +1041,7 @@ static void test_vote_req_same_term_already_voted_different(void)
|
||||||
s.voted_for = 1;
|
s.voted_for = 1;
|
||||||
s.voted_for_term = 5;
|
s.voted_for_term = 5;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 2, 5, &reply_term);
|
int rc = raft_on_vote_request(&s, 2, 5, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||||
|
|
@ -1058,7 +1078,7 @@ static void test_vote_req_same_term_idempotent_same_candidate(void)
|
||||||
s.voted_for = 1;
|
s.voted_for = 1;
|
||||||
s.voted_for_term = 5;
|
s.voted_for_term = 5;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||||
|
|
@ -1089,7 +1109,7 @@ static void test_vote_req_leader_denies(void)
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
s.role = RAFT_LEADER;
|
s.role = RAFT_LEADER;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||||
|
|
@ -1124,7 +1144,7 @@ static void test_vote_req_on_vote_eligible_denies(void)
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||||
|
|
@ -1161,7 +1181,7 @@ static void test_vote_req_on_vote_eligible_grants(void)
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||||
|
|
@ -1198,7 +1218,7 @@ static void test_vote_req_on_vote_eligible_null(void)
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||||
|
|
@ -1228,7 +1248,7 @@ static void test_vote_req_persist_fails(void)
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||||
|
|
@ -1257,14 +1277,14 @@ static void test_vote_req_reply_term_always_set(void)
|
||||||
reset_mock();
|
reset_mock();
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 7));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 7));
|
||||||
reply_term = 0;
|
reply_term = 0;
|
||||||
raft_on_vote_request(&s, 1, 3, &reply_term);
|
raft_on_vote_request(&s, 1, 3, 0, 0, &reply_term);
|
||||||
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal current term=7 */
|
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal current term=7 */
|
||||||
|
|
||||||
/* Path 2: term upgrade + grant */
|
/* Path 2: term upgrade + grant */
|
||||||
reset_mock();
|
reset_mock();
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 3));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 3));
|
||||||
reply_term = 0;
|
reply_term = 0;
|
||||||
raft_on_vote_request(&s, 1, 7, &reply_term);
|
raft_on_vote_request(&s, 1, 7, 0, 0, &reply_term);
|
||||||
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal upgraded term=7 */
|
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal upgraded term=7 */
|
||||||
|
|
||||||
/* Path 3: already voted for different — deny */
|
/* Path 3: already voted for different — deny */
|
||||||
|
|
@ -1272,7 +1292,7 @@ static void test_vote_req_reply_term_always_set(void)
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
s.voted_for = 1; s.voted_for_term = 5;
|
s.voted_for = 1; s.voted_for_term = 5;
|
||||||
reply_term = 0;
|
reply_term = 0;
|
||||||
raft_on_vote_request(&s, 2, 5, &reply_term);
|
raft_on_vote_request(&s, 2, 5, 0, 0, &reply_term);
|
||||||
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal 5 */
|
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal 5 */
|
||||||
|
|
||||||
/* Path 4: persist fails — deny */
|
/* Path 4: persist fails — deny */
|
||||||
|
|
@ -1281,7 +1301,7 @@ static void test_vote_req_reply_term_always_set(void)
|
||||||
g_mock.persist_return_value = -1;
|
g_mock.persist_return_value = -1;
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
reply_term = 0;
|
reply_term = 0;
|
||||||
raft_on_vote_request(&s, 1, 5, &reply_term);
|
raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||||
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal 5 */
|
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal 5 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4098,7 +4118,7 @@ static void test_proto_A3_late_election_denied(void)
|
||||||
|
|
||||||
/* Now node 2 tries to request a vote from node 1 at the same term=1 */
|
/* Now node 2 tries to request a vote from node 1 at the same term=1 */
|
||||||
uint64_t reply_term = 0;
|
uint64_t reply_term = 0;
|
||||||
int rc = raft_on_vote_request(&c.nodes[1].state, 2, 1, &reply_term);
|
int rc = raft_on_vote_request(&c.nodes[1].state, 2, 1, 0, 0, &reply_term);
|
||||||
|
|
||||||
/* Node 1 must deny — already voted for node 0 this term */
|
/* Node 1 must deny — already voted for node 0 this term */
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
|
|
@ -4153,7 +4173,7 @@ static void test_proto_A5_leader_denies_vote_request(void)
|
||||||
|
|
||||||
/* Send a vote request to the leader at the same term */
|
/* Send a vote request to the leader at the same term */
|
||||||
uint64_t reply_term = 0;
|
uint64_t reply_term = 0;
|
||||||
int rc = raft_on_vote_request(&c.nodes[0].state, 1, term_before, &reply_term);
|
int rc = raft_on_vote_request(&c.nodes[0].state, 1, term_before, 0, 0, &reply_term);
|
||||||
|
|
||||||
/* Must be denied */
|
/* Must be denied */
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
|
|
@ -4187,7 +4207,7 @@ static void test_proto_C1_eligible_denies(void)
|
||||||
s.term = 1;
|
s.term = 1;
|
||||||
s.voted_for = -1;
|
s.voted_for = -1;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 1, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 1, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||||
|
|
@ -4214,7 +4234,7 @@ static void test_proto_C2_eligible_grants(void)
|
||||||
s.term = 1;
|
s.term = 1;
|
||||||
s.voted_for = -1;
|
s.voted_for = -1;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 1, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 1, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
TEST_ASSERT_EQUAL_INT(1, s.voted_for);
|
TEST_ASSERT_EQUAL_INT(1, s.voted_for);
|
||||||
|
|
@ -4240,7 +4260,7 @@ static void test_proto_C3_eligible_null_grants(void)
|
||||||
s.term = 1;
|
s.term = 1;
|
||||||
s.voted_for = -1;
|
s.voted_for = -1;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 1, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 1, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
/* vote_eligible not called (NULL) */
|
/* vote_eligible not called (NULL) */
|
||||||
|
|
@ -4267,7 +4287,7 @@ static void test_proto_C4_stale_term_no_eligible_call(void)
|
||||||
s.term = 5;
|
s.term = 5;
|
||||||
s.voted_for = -1;
|
s.voted_for = -1;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 3, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 3, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_INT(0, g_mock.vote_eligible_call_count);
|
TEST_ASSERT_EQUAL_INT(0, g_mock.vote_eligible_call_count);
|
||||||
|
|
@ -4295,7 +4315,7 @@ static void test_proto_C5_already_voted_no_eligible_call(void)
|
||||||
s.voted_for = 1;
|
s.voted_for = 1;
|
||||||
s.voted_for_term = 3;
|
s.voted_for_term = 3;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 2, 3, &reply_term);
|
int rc = raft_on_vote_request(&s, 2, 3, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_INT(0, g_mock.vote_eligible_call_count);
|
TEST_ASSERT_EQUAL_INT(0, g_mock.vote_eligible_call_count);
|
||||||
|
|
@ -4395,7 +4415,7 @@ static void test_proto_H6_restored_vote_blocks_second_vote(void)
|
||||||
TEST_ASSERT_EQUAL_INT(0, raft_init(&s, &cfg));
|
TEST_ASSERT_EQUAL_INT(0, raft_init(&s, &cfg));
|
||||||
|
|
||||||
/* Different candidate (idx=2) requests a vote in the SAME term 5 */
|
/* Different candidate (idx=2) requests a vote in the SAME term 5 */
|
||||||
int rc = raft_on_vote_request(&s, 2, 5, &reply_term);
|
int rc = raft_on_vote_request(&s, 2, 5, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||||
|
|
@ -4405,7 +4425,7 @@ static void test_proto_H6_restored_vote_blocks_second_vote(void)
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, s.voted_for_term);
|
TEST_ASSERT_EQUAL_UINT64(5, s.voted_for_term);
|
||||||
|
|
||||||
/* Same candidate (idx=1) retransmitting must still be re-granted */
|
/* Same candidate (idx=1) retransmitting must still be re-granted */
|
||||||
rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4445,7 +4465,7 @@ static void test_proto_H3_persist_before_vote_recorded(void)
|
||||||
s.term = 1;
|
s.term = 1;
|
||||||
s.voted_for = -1;
|
s.voted_for = -1;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 1, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 1, 0, 0, &reply_term);
|
||||||
|
|
||||||
/* Must grant */
|
/* Must grant */
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
|
|
@ -4481,7 +4501,7 @@ static void test_proto_H4_persist_fail_denies(void)
|
||||||
s.term = 1;
|
s.term = 1;
|
||||||
s.voted_for = -1;
|
s.voted_for = -1;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 1, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 1, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||||
|
|
@ -4602,7 +4622,7 @@ static void test_proto_B2_higher_term_updates_state(void)
|
||||||
s.voted_for = 1; /* node voted for itself in an earlier election */
|
s.voted_for = 1; /* node voted for itself in an earlier election */
|
||||||
s.voted_for_term = 1; /* that vote was in term 1 */
|
s.voted_for_term = 1; /* that vote was in term 1 */
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 0, 5, &reply_term);
|
int rc = raft_on_vote_request(&s, 0, 5, 0, 0, &reply_term);
|
||||||
|
|
||||||
/* Term must be upgraded to 5 */
|
/* Term must be upgraded to 5 */
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||||
|
|
@ -4632,7 +4652,7 @@ static void test_proto_B3_stale_term_denied(void)
|
||||||
reset_mock();
|
reset_mock();
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 0, 3, &reply_term);
|
int rc = raft_on_vote_request(&s, 0, 3, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||||
|
|
@ -4660,7 +4680,7 @@ static void test_proto_B4_different_candidate_denied(void)
|
||||||
s.voted_for = 1;
|
s.voted_for = 1;
|
||||||
s.voted_for_term = 3;
|
s.voted_for_term = 3;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 2, 3, &reply_term);
|
int rc = raft_on_vote_request(&s, 2, 3, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(3, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(3, reply_term);
|
||||||
|
|
@ -4691,7 +4711,7 @@ static void test_proto_B5_idempotent_same_candidate(void)
|
||||||
s.voted_for = 1;
|
s.voted_for = 1;
|
||||||
s.voted_for_term = 3;
|
s.voted_for_term = 3;
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 3, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 3, 0, 0, &reply_term);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
TEST_ASSERT_EQUAL_UINT64(3, reply_term);
|
TEST_ASSERT_EQUAL_UINT64(3, reply_term);
|
||||||
|
|
@ -5055,7 +5075,7 @@ static void test_proto_F2_voted_during_delay_skip(void)
|
||||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||||
|
|
||||||
/* During the delay, a vote request arrives from node 0 at term=1 */
|
/* During the delay, a vote request arrives from node 0 at term=1 */
|
||||||
int rc = raft_on_vote_request(&s, 0, 1, &reply_term);
|
int rc = raft_on_vote_request(&s, 0, 1, 0, 0, &reply_term);
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
TEST_ASSERT_EQUAL_INT(0, s.voted_for);
|
TEST_ASSERT_EQUAL_INT(0, s.voted_for);
|
||||||
|
|
||||||
|
|
@ -5220,6 +5240,279 @@ static void test_proto_G2_partition_minority_no_quorum(void)
|
||||||
|
|
||||||
#endif /* XCE_TESTING */
|
#endif /* XCE_TESTING */
|
||||||
|
|
||||||
|
/* ---------- (lastLogTerm,lastLogIndex) vote freshness tests (task 4.1) ----------
|
||||||
|
*
|
||||||
|
* raft.tla HandleRequestVoteRequest logOk (285), closes G6. The library now
|
||||||
|
* computes logOk itself from the candidate's advertised (cand_last_log_term,
|
||||||
|
* cand_last_log_index) against its own (on_get_entry_term(log_len), log_len),
|
||||||
|
* BEFORE the application gate. */
|
||||||
|
|
||||||
|
/* Configure the voter's local log: last index = log_len, last term = last_term. */
|
||||||
|
static void set_voter_log(raft_state_t *s, uint64_t log_len, uint64_t last_term)
|
||||||
|
{
|
||||||
|
s->log_len = log_len;
|
||||||
|
g_mock.get_entry_term_log_len = log_len;
|
||||||
|
g_mock.local_last_log_term = last_term;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test_vote_req_freshness_stale_term_denied_higher_index
|
||||||
|
* Voter log = (term 5, index 3). Candidate advertises a STALE last-log term (4)
|
||||||
|
* but a HIGHER index (10). logOk false -> deny, even though the app gate grants.
|
||||||
|
* (Req 3.1, 3.3, 3.4.)
|
||||||
|
*/
|
||||||
|
static void test_vote_req_freshness_stale_term_denied_higher_index(void)
|
||||||
|
{
|
||||||
|
raft_state_t s;
|
||||||
|
raft_callbacks_t cbs = make_mock_cbs();
|
||||||
|
uint64_t reply_term = 0;
|
||||||
|
|
||||||
|
cbs.on_trace = mock_trace;
|
||||||
|
cbs.on_vote_eligible = mock_vote_eligible;
|
||||||
|
cbs.on_get_entry_term = mock_get_entry_term;
|
||||||
|
reset_mock();
|
||||||
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
set_voter_log(&s, 3, 5);
|
||||||
|
|
||||||
|
int rc = raft_on_vote_request(&s, 1, 5, 4, 10, &reply_term);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
|
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, g_mock.vote_eligible_call_count);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||||
|
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test_vote_req_freshness_fresher_term_granted_smaller_index
|
||||||
|
* Voter log = (term 5, index 10). Candidate (term 6 fresher, index 2 smaller).
|
||||||
|
* logOk = (6 > 5) -> grant. (Req 3.1, 3.6.)
|
||||||
|
*/
|
||||||
|
static void test_vote_req_freshness_fresher_term_granted_smaller_index(void)
|
||||||
|
{
|
||||||
|
raft_state_t s;
|
||||||
|
raft_callbacks_t cbs = make_mock_cbs();
|
||||||
|
uint64_t reply_term = 0;
|
||||||
|
|
||||||
|
cbs.on_trace = mock_trace;
|
||||||
|
cbs.on_vote_eligible = mock_vote_eligible;
|
||||||
|
cbs.on_get_entry_term = mock_get_entry_term;
|
||||||
|
reset_mock();
|
||||||
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
set_voter_log(&s, 10, 5);
|
||||||
|
|
||||||
|
int rc = raft_on_vote_request(&s, 1, 5, 6, 2, &reply_term);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
|
TEST_ASSERT_EQUAL_INT(1, g_mock.persist_count);
|
||||||
|
TEST_ASSERT_EQUAL_INT(1, s.voted_for);
|
||||||
|
TEST_ASSERT_EQUAL_UINT64(5, s.voted_for_term);
|
||||||
|
TEST_ASSERT_EQUAL_INT(1, g_mock.vote_eligible_call_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test_vote_req_freshness_tie_term_index_boundary
|
||||||
|
* Voter log = (term 5, index 4). On a term TIE, grant requires cand index >=
|
||||||
|
* voter log_len. index 3 (<4) -> deny; index 4 (==4) -> grant. (Req 3.1.)
|
||||||
|
*/
|
||||||
|
static void test_vote_req_freshness_tie_term_index_boundary(void)
|
||||||
|
{
|
||||||
|
raft_state_t s;
|
||||||
|
raft_callbacks_t cbs = make_mock_cbs();
|
||||||
|
uint64_t reply_term = 0;
|
||||||
|
|
||||||
|
cbs.on_vote_eligible = mock_vote_eligible;
|
||||||
|
cbs.on_get_entry_term = mock_get_entry_term;
|
||||||
|
|
||||||
|
reset_mock();
|
||||||
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
set_voter_log(&s, 4, 5);
|
||||||
|
int rc = raft_on_vote_request(&s, 1, 5, 5, 3, &reply_term); /* idx 3 < 4 */
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||||
|
|
||||||
|
reset_mock();
|
||||||
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
set_voter_log(&s, 4, 5);
|
||||||
|
rc = raft_on_vote_request(&s, 1, 5, 5, 4, &reply_term); /* idx 4 == 4 */
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
|
TEST_ASSERT_EQUAL_INT(1, g_mock.persist_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test_vote_req_freshness_empty_voter_log_term_zero
|
||||||
|
* Empty voter log (log_len 0): local last-log term 0. Candidate (0,0) satisfies
|
||||||
|
* logOk = (0>0)||(0==0 && 0>=0) = true -> grant. (Req 3.1.)
|
||||||
|
*/
|
||||||
|
static void test_vote_req_freshness_empty_voter_log_term_zero(void)
|
||||||
|
{
|
||||||
|
raft_state_t s;
|
||||||
|
raft_callbacks_t cbs = make_mock_cbs();
|
||||||
|
uint64_t reply_term = 0;
|
||||||
|
|
||||||
|
cbs.on_vote_eligible = mock_vote_eligible;
|
||||||
|
cbs.on_get_entry_term = mock_get_entry_term;
|
||||||
|
reset_mock();
|
||||||
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
|
TEST_ASSERT_EQUAL_INT(1, g_mock.persist_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test_vote_req_freshness_logok_precedes_app_gate
|
||||||
|
* !logOk denies even when on_vote_eligible WOULD grant, and the app gate is not
|
||||||
|
* consulted (logOk strictly precedes it). (Req 3.4.)
|
||||||
|
*/
|
||||||
|
static void test_vote_req_freshness_logok_precedes_app_gate(void)
|
||||||
|
{
|
||||||
|
raft_state_t s;
|
||||||
|
raft_callbacks_t cbs = make_mock_cbs();
|
||||||
|
uint64_t reply_term = 0;
|
||||||
|
|
||||||
|
cbs.on_vote_eligible = mock_vote_eligible;
|
||||||
|
cbs.on_get_entry_term = mock_get_entry_term;
|
||||||
|
reset_mock();
|
||||||
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
set_voter_log(&s, 8, 7);
|
||||||
|
|
||||||
|
int rc = raft_on_vote_request(&s, 1, 5, 7, 1, &reply_term);
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, g_mock.vote_eligible_call_count);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- (lastLogTerm,lastLogIndex) PreVote freshness tests (task 5.1) ----------
|
||||||
|
*
|
||||||
|
* The PreVote path applies the IDENTICAL logOk, and mutates NOTHING. (Req 4.1-4.5.) */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test_pre_vote_req_freshness_stale_tuple_denied
|
||||||
|
* Voter (term 5, index 3). PreVote tuple (term 4 stale, index 10) -> deny, no
|
||||||
|
* mutation, no persist. (Req 4.1, 4.3.)
|
||||||
|
*/
|
||||||
|
static void test_pre_vote_req_freshness_stale_tuple_denied(void)
|
||||||
|
{
|
||||||
|
raft_state_t s;
|
||||||
|
raft_callbacks_t cbs = make_mock_cbs();
|
||||||
|
|
||||||
|
cbs.on_vote_eligible = mock_vote_eligible;
|
||||||
|
cbs.on_get_entry_term = mock_get_entry_term;
|
||||||
|
reset_mock();
|
||||||
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
set_voter_log(&s, 3, 5);
|
||||||
|
|
||||||
|
int rc = raft_on_pre_vote_request(&s, 1, 5, 4, 10);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
|
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||||
|
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||||
|
TEST_ASSERT_EQUAL_UINT64(0, s.voted_for_term);
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, s.role);
|
||||||
|
TEST_ASSERT_EQUAL_UINT64(0, s.commit_index);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test_pre_vote_req_freshness_fresher_tuple_granted
|
||||||
|
* Voter (term 5, index 10). PreVote (term 6 fresher, index 2) -> grant, still
|
||||||
|
* side-effect-free. (Req 4.1.)
|
||||||
|
*/
|
||||||
|
static void test_pre_vote_req_freshness_fresher_tuple_granted(void)
|
||||||
|
{
|
||||||
|
raft_state_t s;
|
||||||
|
raft_callbacks_t cbs = make_mock_cbs();
|
||||||
|
|
||||||
|
cbs.on_vote_eligible = mock_vote_eligible;
|
||||||
|
cbs.on_get_entry_term = mock_get_entry_term;
|
||||||
|
reset_mock();
|
||||||
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
set_voter_log(&s, 10, 5);
|
||||||
|
|
||||||
|
int rc = raft_on_pre_vote_request(&s, 1, 5, 6, 2);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||||
|
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test_pre_vote_req_freshness_no_mutation_grant_and_deny
|
||||||
|
* Across BOTH a grant and a deny, no change to term/voted_for/voted_for_term/
|
||||||
|
* role/commit_index and no on_persist. (Req 4.4.)
|
||||||
|
*/
|
||||||
|
static void test_pre_vote_req_freshness_no_mutation_grant_and_deny(void)
|
||||||
|
{
|
||||||
|
raft_state_t s;
|
||||||
|
raft_callbacks_t cbs = make_mock_cbs();
|
||||||
|
|
||||||
|
cbs.on_vote_eligible = mock_vote_eligible;
|
||||||
|
cbs.on_get_entry_term = mock_get_entry_term;
|
||||||
|
|
||||||
|
reset_mock();
|
||||||
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
s.commit_index = 2;
|
||||||
|
set_voter_log(&s, 4, 5);
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, raft_on_pre_vote_request(&s, 1, 5, 5, 9));
|
||||||
|
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||||
|
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||||
|
TEST_ASSERT_EQUAL_UINT64(0, s.voted_for_term);
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, s.role);
|
||||||
|
TEST_ASSERT_EQUAL_UINT64(2, s.commit_index);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||||
|
|
||||||
|
reset_mock();
|
||||||
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
s.commit_index = 2;
|
||||||
|
set_voter_log(&s, 4, 5);
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, raft_on_pre_vote_request(&s, 1, 5, 5, 1));
|
||||||
|
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||||
|
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||||
|
TEST_ASSERT_EQUAL_UINT64(0, s.voted_for_term);
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, s.role);
|
||||||
|
TEST_ASSERT_EQUAL_UINT64(2, s.commit_index);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test_pre_vote_req_freshness_stale_term_still_denied
|
||||||
|
* The pre-existing stale-term deny stays in force even with a fresh tuple:
|
||||||
|
* cand_term < s.term denies. (Req 4.5.)
|
||||||
|
*/
|
||||||
|
static void test_pre_vote_req_freshness_stale_term_still_denied(void)
|
||||||
|
{
|
||||||
|
raft_state_t s;
|
||||||
|
raft_callbacks_t cbs = make_mock_cbs();
|
||||||
|
|
||||||
|
cbs.on_vote_eligible = mock_vote_eligible;
|
||||||
|
cbs.on_get_entry_term = mock_get_entry_term;
|
||||||
|
reset_mock();
|
||||||
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
set_voter_log(&s, 3, 5);
|
||||||
|
|
||||||
|
int rc = raft_on_pre_vote_request(&s, 1, 4, 99, 99);
|
||||||
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- raft_on_pre_vote_request tests (task 2.1) ---------- */
|
/* ---------- raft_on_pre_vote_request tests (task 2.1) ---------- */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -5249,7 +5542,7 @@ static void test_pre_vote_req_grant_higher_term_eligible(void)
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
|
||||||
int rc = raft_on_pre_vote_request(&s, 1, 6);
|
int rc = raft_on_pre_vote_request(&s, 1, 6, 0, 0);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
|
|
||||||
|
|
@ -5283,7 +5576,7 @@ static void test_pre_vote_req_grant_equal_term_eligible(void)
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
|
||||||
int rc = raft_on_pre_vote_request(&s, 1, 5); /* equal term */
|
int rc = raft_on_pre_vote_request(&s, 1, 5, 0, 0); /* equal term */
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
|
|
||||||
|
|
@ -5312,7 +5605,7 @@ static void test_pre_vote_req_deny_stale_term(void)
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
|
||||||
int rc = raft_on_pre_vote_request(&s, 1, 4); /* stale */
|
int rc = raft_on_pre_vote_request(&s, 1, 4, 0, 0); /* stale */
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
|
|
||||||
|
|
@ -5346,7 +5639,7 @@ static void test_pre_vote_req_deny_eligibility_rejects(void)
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
|
||||||
int rc = raft_on_pre_vote_request(&s, 1, 6);
|
int rc = raft_on_pre_vote_request(&s, 1, 6, 0, 0);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||||
TEST_ASSERT_EQUAL_INT(1, g_mock.vote_eligible_call_count);
|
TEST_ASSERT_EQUAL_INT(1, g_mock.vote_eligible_call_count);
|
||||||
|
|
@ -5375,7 +5668,7 @@ static void test_pre_vote_req_null_eligible_grants(void)
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||||
|
|
||||||
int rc = raft_on_pre_vote_request(&s, 1, 6);
|
int rc = raft_on_pre_vote_request(&s, 1, 6, 0, 0);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
|
|
||||||
|
|
@ -5409,7 +5702,7 @@ static void test_pre_vote_req_grant_preserves_existing_vote(void)
|
||||||
s.voted_for_term = 5;
|
s.voted_for_term = 5;
|
||||||
|
|
||||||
/* A different candidate (peer 2) probes a PreVote for term 6. */
|
/* A different candidate (peer 2) probes a PreVote for term 6. */
|
||||||
int rc = raft_on_pre_vote_request(&s, 2, 6);
|
int rc = raft_on_pre_vote_request(&s, 2, 6, 0, 0);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||||
|
|
||||||
|
|
@ -5940,14 +6233,14 @@ static void test_invariant_prevote_sequence_pure_no_persist(void)
|
||||||
TEST_ASSERT_EQUAL_INT(0, raft_begin_pre_vote(&s));
|
TEST_ASSERT_EQUAL_INT(0, raft_begin_pre_vote(&s));
|
||||||
|
|
||||||
/* (b) answer an incoming PreVote request that PASSES eligibility — grant */
|
/* (b) answer an incoming PreVote request that PASSES eligibility — grant */
|
||||||
TEST_ASSERT_EQUAL_INT(1, raft_on_pre_vote_request(&s, 2, 6 /* higher */));
|
TEST_ASSERT_EQUAL_INT(1, raft_on_pre_vote_request(&s, 2, 6 /* higher */, 0, 0));
|
||||||
|
|
||||||
/* (c) answer a STALE-term incoming request — deny */
|
/* (c) answer a STALE-term incoming request — deny */
|
||||||
TEST_ASSERT_EQUAL_INT(0, raft_on_pre_vote_request(&s, 2, 3 /* stale */));
|
TEST_ASSERT_EQUAL_INT(0, raft_on_pre_vote_request(&s, 2, 3 /* stale */, 0, 0));
|
||||||
|
|
||||||
/* (d) answer an incoming request the eligibility gate REJECTS — deny */
|
/* (d) answer an incoming request the eligibility gate REJECTS — deny */
|
||||||
g_mock.vote_eligible_return_value = 0;
|
g_mock.vote_eligible_return_value = 0;
|
||||||
TEST_ASSERT_EQUAL_INT(0, raft_on_pre_vote_request(&s, 2, 6));
|
TEST_ASSERT_EQUAL_INT(0, raft_on_pre_vote_request(&s, 2, 6, 0, 0));
|
||||||
g_mock.vote_eligible_return_value = 1;
|
g_mock.vote_eligible_return_value = 1;
|
||||||
|
|
||||||
/* (e) tally replies: a grant (drives to WON quorum on 3-node), then a
|
/* (e) tally replies: a grant (drives to WON quorum on 3-node), then a
|
||||||
|
|
@ -6103,7 +6396,7 @@ static void test_invariant_real_vote_higher_term_adopt_unchanged(void)
|
||||||
s.voted_for_term = 2;
|
s.voted_for_term = 2;
|
||||||
reset_mock();
|
reset_mock();
|
||||||
|
|
||||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||||
|
|
||||||
/* Higher-term adopt + leader step-down, exactly as bug-339/340 require. */
|
/* Higher-term adopt + leader step-down, exactly as bug-339/340 require. */
|
||||||
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||||
|
|
@ -6229,6 +6522,19 @@ void test_raft(void)
|
||||||
RUN_TEST(test_vote_req_persist_fails);
|
RUN_TEST(test_vote_req_persist_fails);
|
||||||
RUN_TEST(test_vote_req_reply_term_always_set);
|
RUN_TEST(test_vote_req_reply_term_always_set);
|
||||||
|
|
||||||
|
/* (lastLogTerm,lastLogIndex) binding-vote freshness (task 4.1, Req 3.1-3.6) */
|
||||||
|
RUN_TEST(test_vote_req_freshness_stale_term_denied_higher_index);
|
||||||
|
RUN_TEST(test_vote_req_freshness_fresher_term_granted_smaller_index);
|
||||||
|
RUN_TEST(test_vote_req_freshness_tie_term_index_boundary);
|
||||||
|
RUN_TEST(test_vote_req_freshness_empty_voter_log_term_zero);
|
||||||
|
RUN_TEST(test_vote_req_freshness_logok_precedes_app_gate);
|
||||||
|
|
||||||
|
/* (lastLogTerm,lastLogIndex) PreVote freshness (task 5.1, Req 4.1-4.5) */
|
||||||
|
RUN_TEST(test_pre_vote_req_freshness_stale_tuple_denied);
|
||||||
|
RUN_TEST(test_pre_vote_req_freshness_fresher_tuple_granted);
|
||||||
|
RUN_TEST(test_pre_vote_req_freshness_no_mutation_grant_and_deny);
|
||||||
|
RUN_TEST(test_pre_vote_req_freshness_stale_term_still_denied);
|
||||||
|
|
||||||
/* raft_on_election_timeout, raft_on_leader_lost, raft_check_and_start_election (task 3.5) */
|
/* raft_on_election_timeout, raft_on_leader_lost, raft_check_and_start_election (task 3.5) */
|
||||||
RUN_TEST(test_election_timeout_undecided);
|
RUN_TEST(test_election_timeout_undecided);
|
||||||
RUN_TEST(test_election_timeout_already_decided);
|
RUN_TEST(test_election_timeout_already_decided);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue