From 92e21ceb22869e693180dae25a9ca53625ed5e5e Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Thu, 25 Jan 2024 20:06:27 +0000 Subject: [PATCH] Add raft_voter_contact() 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. --- Makefile.am | 3 +- include/raft.h | 18 ++++- src/convert.c | 3 + src/raft.c | 10 +++ src/tick.c | 1 + test/integration/test_voter_contacts.c | 103 +++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 test/integration/test_voter_contacts.c diff --git a/Makefile.am b/Makefile.am index bdcb1f358..f57fd3e34 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/include/raft.h b/include/raft.h index 362ea434e..c6da50f80 100644 --- a/include/raft.h +++ b/include/raft.h @@ -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; }; @@ -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. diff --git a/src/convert.c b/src/convert.c index 4c084a1e1..44f8f058c 100644 --- a/src/convert.c +++ b/src/convert.c @@ -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) { diff --git a/src/raft.c b/src/raft.c index b035413b8..76be35202 100644 --- a/src/raft.c +++ b/src/raft.c @@ -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; @@ -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; diff --git a/src/tick.c b/src/tick.c index 7f0273676..5f0a91c3e 100644 --- a/src/tick.c +++ b/src/tick.c @@ -107,6 +107,7 @@ static bool checkContactQuorum(struct raft *r) contacts++; } } + r->leader_state.voter_contacts = contacts; return contacts > configurationVoterCount(&r->configuration) / 2; } diff --git a/test/integration/test_voter_contacts.c b/test/integration/test_voter_contacts.c new file mode 100644 index 000000000..106c66634 --- /dev/null +++ b/test/integration/test_voter_contacts.c @@ -0,0 +1,103 @@ +#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