Skip to content
This repository has been archived by the owner on Mar 4, 2024. It is now read-only.

Commit

Permalink
Add raft_voter_contact()
Browse files Browse the repository at this point in the history
This returns the number of voting nodes that are recently in contact
with the leader, to allow determining if the cluster is currently in a
degraded / at risk state.
  • Loading branch information
ralight committed Jan 25, 2024
1 parent d67cffd commit 73e8dc4
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 2 deletions.
3 changes: 2 additions & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ test_integration_core_SOURCES = \
test/integration/test_start.c \
test/integration/test_strerror.c \
test/integration/test_tick.c \
test/integration/test_transfer.c
test/integration/test_transfer.c \
test/integration/test_voter_contacts.c
test_integration_core_CFLAGS = $(AM_CFLAGS) -Wno-conversion
test_integration_core_LDFLAGS = -no-install
test_integration_core_LDADD = libtest.la libraft.la
Expand Down
18 changes: 17 additions & 1 deletion include/raft.h
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,9 @@ struct raft
raft_index round_index; /* Target of the current round. */
raft_time round_start; /* Start of current round. */
void *requests[2]; /* Outstanding client requests. */
uint64_t reserved[8]; /* Future use */
uint32_t voter_contacts; /* Current number of voting nodes we are in contact with */
uint32_t reserved2; /* Future use */
uint64_t reserved[7]; /* Future use */
} leader_state;
};

Expand Down Expand Up @@ -981,6 +983,20 @@ RAFT_API raft_index raft_last_index(struct raft *r);
*/
RAFT_API raft_index raft_last_applied(struct raft *r);

/**
* Return the number of voting servers that the leader has recently been in
* contact with. This can be used to help determine whether the cluster may be
* in a degraded/at risk state.
*
* Returns valid values >= 1, because a leader is always in contact with
* itself.
* Returns -1 if called on a follower.
*
* Note that the value returned may be out of date, and so should not be relied
* upon for absolute correctness.
*/
RAFT_API int raft_voter_contacts(struct raft *r);

/**
* Common fields across client request types.
* `req_id`, `client_id` and `unique_id` are currently unused.
Expand Down
3 changes: 3 additions & 0 deletions src/convert.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ static void convertSetState(struct raft *r, unsigned short new_state)
(r->state == RAFT_CANDIDATE && new_state == RAFT_UNAVAILABLE) ||
(r->state == RAFT_LEADER && new_state == RAFT_UNAVAILABLE));
r->state = new_state;
if (r->state == RAFT_LEADER) {
r->leader_state.voter_contacts = 1;
}

struct raft_callbacks *cbs = raftGetCallbacks(r);
if (cbs != NULL && cbs->state_cb != NULL) {
Expand Down
10 changes: 10 additions & 0 deletions src/raft.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ int raft_init(struct raft *r,
r->last_applied = 0;
r->last_stored = 0;
r->state = RAFT_UNAVAILABLE;
r->leader_state.voter_contacts = 0;
rv = raftInitCallbacks(r);
if (rv != 0) {
goto err_after_address_alloc;
Expand Down Expand Up @@ -191,6 +192,15 @@ const char *raft_errmsg(struct raft *r)
return r->errmsg;
}

int raft_voter_contacts(struct raft *r)
{
if (r->state == RAFT_LEADER) {
return (int)r->leader_state.voter_contacts;
} else {
return -1;
}
}

int raft_bootstrap(struct raft *r, const struct raft_configuration *conf)
{
int rv;
Expand Down
1 change: 1 addition & 0 deletions src/tick.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ static bool checkContactQuorum(struct raft *r)
contacts++;
}
}
r->leader_state.voter_contacts = contacts;

return contacts > configurationVoterCount(&r->configuration) / 2;
}
Expand Down
105 changes: 105 additions & 0 deletions test/integration/test_voter_contacts.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "../lib/cluster.h"
#include "../lib/runner.h"

#define N_SERVERS 3

/******************************************************************************
*
* Fixture with a test raft cluster.
*
*****************************************************************************/

struct fixture
{
FIXTURE_CLUSTER;
};

/******************************************************************************
*
* Helper macros
*
*****************************************************************************/

#define STEP_N(N) raft_fixture_step_n(&f->cluster, N)

/******************************************************************************
*
* Set up a cluster with a three servers.
*
*****************************************************************************/

static void *setUp(const MunitParameter params[], MUNIT_UNUSED void *user_data)
{
struct fixture *f = munit_malloc(sizeof *f);
SETUP_CLUSTER(N_SERVERS);
CLUSTER_BOOTSTRAP;
CLUSTER_START;
CLUSTER_ELECT(0);
return f;
}

static void tearDown(void *data)
{
struct fixture *f = data;
TEAR_DOWN_CLUSTER;
free(f);
}

/******************************************************************************
*
* raft_voter_contacts
*
*****************************************************************************/

SUITE(raft_voter_contacts)

TEST(raft_voter_contacts, upToDate, setUp, tearDown, 0, NULL)
{
struct fixture *f = data;

CLUSTER_STEP_UNTIL_HAS_LEADER(1000);
CLUSTER_STEP_N(1000);

/* N node cluster with leader */
for (unsigned int i = 0; i < N_SERVERS; i++) {
int count = raft_voter_contacts(CLUSTER_RAFT(i));
if (i == CLUSTER_LEADER) {
munit_assert_int(count, ==, N_SERVERS);
} else {
munit_assert_int(count, ==, -1);
}
}

/* Kill the cluster leader, so a new leader is elected and the number of
* voters should be decreased */
unsigned int leader = CLUSTER_LEADER;
CLUSTER_KILL(leader);
CLUSTER_STEP_UNTIL_HAS_LEADER(1000);
CLUSTER_STEP_N(1000);

for (unsigned int i = 0; i < N_SERVERS; i++) {
if (i == leader) {
continue;
}
int count = raft_voter_contacts(CLUSTER_RAFT(i));
if (i == CLUSTER_LEADER) {
munit_assert_int(count, ==, N_SERVERS - 1);
} else {
munit_assert_int(count, ==, -1);
}
}

/* Revive the old leader, so the count should go back up */
CLUSTER_REVIVE(leader);
CLUSTER_STEP_N(1000);
for (unsigned int i = 0; i < N_SERVERS; i++) {
int count = raft_voter_contacts(CLUSTER_RAFT(i));
if (i == CLUSTER_LEADER) {
munit_assert_int(count, ==, N_SERVERS);
} else {
munit_assert_int(count, ==, -1);
}
}

return MUNIT_OK;
}

0 comments on commit 73e8dc4

Please sign in to comment.