diff --git a/fs/nfs/Kconfig b/fs/nfs/Kconfig index 93d6c4913f2fc8727445d1239bde2302768231d2..e29c2496cb78da05d4ed67ffb00500c3c53b63fb 100644 --- a/fs/nfs/Kconfig +++ b/fs/nfs/Kconfig @@ -225,3 +225,16 @@ config ENFS This feature will improve performance and reliability. If sure, say Y. + +config ENFS_KUNIT_TEST + bool "This builds the ENFS KUnit tests" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + Only useful for kernel devs running KUnit test harness and are not + for inclusion into a production build. + + For more information on KUnit and unit tests in general please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. diff --git a/fs/nfs/enfs/Makefile b/fs/nfs/enfs/Makefile index d42de6e2ca3a34d83a1d68203bc43ddeb0f6da6e..efeb7b5023d9b28eeb0924111c3ccc00c1f879ef 100644 --- a/fs/nfs/enfs/Makefile +++ b/fs/nfs/enfs/Makefile @@ -23,3 +23,4 @@ enfs-y += exten_call.o enfs-y += shard_route.o enfs-y += dns_process.o enfs-y += enfs_lookup_cache.o +enfs-$(CONFIG_ENFS_KUNIT_TEST) += enfs_test.o diff --git a/fs/nfs/enfs/enfs.h b/fs/nfs/enfs/enfs.h index 78b31e14dc9a8aca19ab481e2df5ff6ec9c2b30c..ba6ad1a85d20869512ffbb3af8baa4e98c225656 100644 --- a/fs/nfs/enfs/enfs.h +++ b/fs/nfs/enfs/enfs.h @@ -33,6 +33,9 @@ #define ENFS_MAX_MOUNT_COUNT 256 #define EXTEND_MAX_DNS_NAME_LEN 256 +#define ENFS_UNSTABLE_STATE_TIMEOUT (30 * 60) /* seconds */ +#define ENFS_RECONNECT_TIME_CNT 3 + struct nfs_ip_list { int count; struct sockaddr_storage address[MAX_SUPPORTED_REMOTE_IP_COUNT]; @@ -48,6 +51,14 @@ struct enfs_route_dns_info { struct enfs_dns_info_single routeRemoteDnsList[MAX_DNS_SUPPORTED]; }; +struct enfs_reconnect_time { + s64 time[ENFS_RECONNECT_TIME_CNT + 1]; + s8 head; + s8 tail; + s8 reserve[2]; + unsigned int xprt_cookie; +}; + struct rpc_iostats; struct enfs_xprt_context { int version; @@ -62,7 +73,8 @@ struct enfs_xprt_context { uint32_t cpuId; u32 protocol; // TCP or UDP or RDMA int64_t lastTime; - u32 reverse[40]; + struct enfs_reconnect_time reconnect_time; + u32 reserve[30]; }; static inline bool enfs_is_main_xprt(struct rpc_xprt *xprt) diff --git a/fs/nfs/enfs/enfs_multipath.c b/fs/nfs/enfs/enfs_multipath.c index b22e7bc7a91747f3407796822245bb7c85fcc05a..dc0329136f3eb84ec6a382b7092a138617634fb0 100644 --- a/fs/nfs/enfs/enfs_multipath.c +++ b/fs/nfs/enfs/enfs_multipath.c @@ -473,7 +473,7 @@ static void enfs_add_xprts_to_clnt(struct rpc_clnt *clnt, state = pm_get_path_state(xprt); if (link_count < maxCountPerMount && - (state == PM_STATE_NORMAL || + (enfs_is_path_connected(state) || enfs_get_create_path_no_route()) && limit_local_addr(ip_list, xprt) && enfs_link_count_add(1)) { enfs_xprt_switch_add_xprt(clnt, xprt); diff --git a/fs/nfs/enfs/enfs_roundrobin.c b/fs/nfs/enfs/enfs_roundrobin.c index 8129c461e24dc613ec983775dc2d6087a42ec700..2eca011b40ae88a44ad53efec396d5a149206704 100644 --- a/fs/nfs/enfs/enfs_roundrobin.c +++ b/fs/nfs/enfs/enfs_roundrobin.c @@ -31,7 +31,7 @@ static bool enfs_xprt_is_active(struct rpc_xprt *xprt) return false; state = pm_get_path_state(xprt); - if (state == PM_STATE_NORMAL) + if (enfs_is_path_connected(state)) return true; return false; diff --git a/fs/nfs/enfs/enfs_test.c b/fs/nfs/enfs/enfs_test.c new file mode 100644 index 0000000000000000000000000000000000000000..78f506558a65c8f4f512cf23e4b141a0c7b73830 --- /dev/null +++ b/fs/nfs/enfs/enfs_test.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 +#include + +bool enfs_test_reconnect_time(void); + +static void enfs_unstable_state_test(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, true, enfs_test_reconnect_time()); +} + +static struct kunit_case enfs_test_cases[] = { + KUNIT_CASE(enfs_unstable_state_test), + {} +}; + +static struct kunit_suite enfs_suite = { + .name = "enfs", + .test_cases = enfs_test_cases, +}; + +kunit_test_suite(enfs_suite); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("KUnit tests for ENFS"); diff --git a/fs/nfs/enfs/pm_ping.c b/fs/nfs/enfs/pm_ping.c index 2dd69a7af5ac1926da1d7df02daa0dab680fa81c..42e37b48b7dc76238fe001172cba9d9ab09f7786 100644 --- a/fs/nfs/enfs/pm_ping.c +++ b/fs/nfs/enfs/pm_ping.c @@ -116,6 +116,160 @@ static void set_xprt_close_wait(struct rpc_xprt *xprt) } +static inline s8 enfs_get_next_time_idx(s8 idx) +{ + return (idx + 1) % (ENFS_RECONNECT_TIME_CNT + 1); +} + +static inline bool enfs_is_time_buf_empty(struct enfs_reconnect_time *time) +{ + return time->head == time->tail; +} + +static inline bool enfs_is_time_buf_full(struct enfs_reconnect_time *time) +{ + return enfs_get_next_time_idx(time->head) == time->tail; +} + +static void +enfs_update_reconnect_time( + struct enfs_reconnect_time *time, + s64 now_ms, + unsigned int cookie) +{ + bool is_reconnect = false; + + if (time->xprt_cookie && time->xprt_cookie != cookie) + is_reconnect = true; + time->xprt_cookie = cookie; + if (is_reconnect) { + /* store reconnect time */ + time->time[time->head] = now_ms; + time->head = enfs_get_next_time_idx(time->head); + /* array is full */ + if (time->head == time->tail) + time->tail = enfs_get_next_time_idx(time->tail); + } + + while (time->tail != time->head) { + if (now_ms - time->time[time->tail] < + ENFS_UNSTABLE_STATE_TIMEOUT * 1000) + break; + /* timed out */ + time->tail = enfs_get_next_time_idx(time->tail); + } +} + +static void enfs_check_reconnect(struct rpc_xprt *xprt) +{ + struct enfs_xprt_context *ctx = NULL; + struct enfs_reconnect_time *time; + bool is_empty, is_full, is_normal; + enum enfs_path_state curr_state = pm_get_path_state(xprt); + + xprt_get(xprt); + + ctx = (struct enfs_xprt_context *)xprt_get_reserve_context(xprt); + if (ctx == NULL) { + enfs_log_error("The xprt multipath ctx is not valid.\n"); + goto out; + } + + time = &ctx->reconnect_time; + enfs_update_reconnect_time(time, ktime_to_ms(ktime_get()), xprt->connect_cookie); + is_empty = enfs_is_time_buf_empty(time); + is_full = enfs_is_time_buf_full(time); + + is_normal = curr_state == PM_STATE_INIT || + (is_empty && curr_state == PM_STATE_UNSTABLE) || + (!is_full && curr_state == PM_STATE_NORMAL); + if (is_normal) + pm_set_path_state(xprt, PM_STATE_NORMAL); + else + pm_set_path_state(xprt, PM_STATE_UNSTABLE); + +out: + xprt_put(xprt); +} + +#if IS_ENABLED(CONFIG_ENFS_KUNIT_TEST) +bool enfs_test_reconnect_time(void) +{ + bool ret = true; + bool match; + s64 begin_ms = ktime_to_ms(ktime_get()); + s64 ms; + unsigned int cookie = 1; + struct enfs_reconnect_time time = { + .head = 0, + .tail = 0, + .xprt_cookie = 0, + }; + + enfs_log_info("begin time: %lld ms\n", begin_ms); + + ms = begin_ms; + enfs_log_info("%lld ms, cookie:%d\n", ms, cookie); + enfs_update_reconnect_time(&time, ms, cookie); + match = enfs_is_time_buf_empty(&time); + if (!match) + return false; + + ms = begin_ms + 1000; + cookie += 1; + enfs_log_info("%lld ms, cookie:%d\n", ms, cookie); + enfs_update_reconnect_time(&time, ms, cookie); + match = time.head == 1 && time.tail == 0; + if (!match) + return false; + + ms = begin_ms + 2000; + cookie += 1; + enfs_log_info("%lld ms, cookie:%d\n", ms, cookie); + enfs_update_reconnect_time(&time, ms, cookie); + match = time.head == 2 && time.tail == 0; + if (!match) + return false; + + ms = begin_ms + 3000; + cookie += 1; + enfs_log_info("%lld ms, cookie:%d\n", ms, cookie); + enfs_update_reconnect_time(&time, ms, cookie); + match = time.head == 3 && time.tail == 0; + if (!match) + return false; + + ms = begin_ms + 4000; + cookie += 1; + enfs_log_info("%lld ms, cookie:%d\n", ms, cookie); + enfs_update_reconnect_time(&time, ms, cookie); + match = time.head == 0 && time.tail == 1; + if (!match) + return false; + match = enfs_is_time_buf_full(&time); + if (!match) + return false; + + ms = begin_ms + 5000; + /* cookie remains unchanged */ + enfs_log_info("%lld ms, cookie:%d\n", ms, cookie); + enfs_update_reconnect_time(&time, ms, cookie); + match = time.head == 0 && time.tail == 1; + if (!match) + return false; + + ms = begin_ms + 5000 + ENFS_UNSTABLE_STATE_TIMEOUT * 1000 + 1; + cookie += 1; + enfs_log_info("%lld ms, cookie:%d\n", ms, cookie); + enfs_update_reconnect_time(&time, ms, cookie); + match = time.head == 1 && time.tail == 0; + if (!match) + return false; + + return ret; +} +#endif + // Default callback for async RPC calls static void pm_ping_call_done(struct rpc_task *task, void *data) { @@ -125,8 +279,7 @@ static void pm_ping_call_done(struct rpc_task *task, void *data) atomic_dec(&check_xprt_count); if (task->tk_status >= 0) { - // xprt is not used,just match paramete. - pm_set_path_state(xprt, PM_STATE_NORMAL); + enfs_check_reconnect(xprt); } else { set_xprt_close_wait(xprt); pm_set_path_state(xprt, PM_STATE_FAULT); @@ -264,7 +417,6 @@ static int pm_ping_add_work(struct rpc_clnt *clnt, struct rpc_xprt *xprt, static int pm_ping_execute_xprt_test(struct rpc_clnt *clnt, struct rpc_xprt *xprt, void *data) { - pm_ping_add_work(clnt, xprt, data); // return 0 for rpc_clnt_iterate_for_each_xprt(clnt, // pm_ping_execute_xprt_test, NULL); because negative value will stop diff --git a/fs/nfs/enfs/pm_state.c b/fs/nfs/enfs/pm_state.c index 9510aac5f7758b3d56443b32cbe605e82e890eff..81f18bf2e920cca97f9131f467b427883164d914 100644 --- a/fs/nfs/enfs/pm_state.c +++ b/fs/nfs/enfs/pm_state.c @@ -92,15 +92,13 @@ void pm_set_path_state(struct rpc_xprt *xprt, enum enfs_path_state state) ctx = (struct enfs_xprt_context *)xprt_get_reserve_context(xprt); if (ctx == NULL) { enfs_log_error("The xprt multipath ctx is not valid.\n"); - xprt_put(xprt); - return; + goto out; } cur_state = atomic_read(&ctx->path_state); - if (cur_state == state) { - xprt_put(xprt); - return; - } + if (cur_state == state) + goto out; + atomic_set(&ctx->path_state, state); ret = sockaddr_ip_to_str((struct sockaddr *)&xprt->addr, remoteip, @@ -123,8 +121,8 @@ void pm_set_path_state(struct rpc_xprt *xprt, enum enfs_path_state state) ("The xprt localip{%s} remoteip{%s} path state change from {%d} to {%d}.\n", localip, remoteip, cur_state, state); +out: xprt_put(xprt); - } void pm_get_path_state_desc(struct rpc_xprt *xprt, char *buf, int len) @@ -150,6 +148,9 @@ void pm_get_path_state_desc(struct rpc_xprt *xprt, char *buf, int len) case PM_STATE_NORMAL: (void)snprintf(buf, len, "Normal"); break; + case PM_STATE_UNSTABLE: + (void)snprintf(buf, len, "Unstable"); + break; case PM_STATE_FAULT: (void)snprintf(buf, len, "Fault"); break; diff --git a/fs/nfs/enfs/pm_state.h b/fs/nfs/enfs/pm_state.h index 7d5743984d9daf495fcfe6875cc6e8d9ed7d0859..6e465506256228edc47f8ab1e2ce382107a51632 100644 --- a/fs/nfs/enfs/pm_state.h +++ b/fs/nfs/enfs/pm_state.h @@ -12,6 +12,7 @@ enum enfs_path_state { PM_STATE_INIT, PM_STATE_NORMAL, + PM_STATE_UNSTABLE, PM_STATE_FAULT, PM_STATE_UNDEFINED // xprt is not multipath xprt }; @@ -19,6 +20,13 @@ enum enfs_path_state { void pm_set_path_state(struct rpc_xprt *xprt, enum enfs_path_state state); enum enfs_path_state pm_get_path_state(struct rpc_xprt *xprt); +static inline bool enfs_is_path_connected(enum enfs_path_state state) +{ + if (state == PM_STATE_NORMAL || state == PM_STATE_UNSTABLE) + return true; + return false; +} + void pm_get_path_state_desc(struct rpc_xprt *xprt, char *buf, int len); void pm_get_xprt_state_desc(struct rpc_xprt *xprt, char *buf, int len); diff --git a/fs/nfs/enfs/shard_route.c b/fs/nfs/enfs/shard_route.c index 3e3e1fb32d5cd285381839d81bdf42e201b0ace1..a4db0e0d88a451dd7a8b751d0f4a83ae24a6d7b3 100644 --- a/fs/nfs/enfs/shard_route.c +++ b/fs/nfs/enfs/shard_route.c @@ -733,20 +733,32 @@ static int get_uuid_from_task(struct rpc_clnt *clnt, struct rpc_task *task, return 0; } -static struct rpc_xprt *choose_less_queue_len(struct rpc_xprt *xport1, - struct rpc_xprt *xport2) +static struct rpc_xprt * +enfs_choose_better_xprt( + struct rpc_xprt *xport1, + struct rpc_xprt *xport2) { struct enfs_xprt_context *context1; struct enfs_xprt_context *context2; + bool is_xprt1_normal, is_xprt2_normal; if (xport1 == NULL) return xport2; if (xport2 == NULL) return xport1; + context1 = (struct enfs_xprt_context *)xprt_get_reserve_context(xport1); context2 = (struct enfs_xprt_context *)xprt_get_reserve_context(xport2); + + is_xprt1_normal = atomic_read(&context1->path_state) == PM_STATE_NORMAL; + is_xprt2_normal = atomic_read(&context2->path_state) == PM_STATE_NORMAL; + if (is_xprt1_normal && !is_xprt2_normal) + return xport1; + if (!is_xprt1_normal && is_xprt2_normal) + return xport2; + if (atomic_long_read(&(context1->queuelen)) > - atomic_long_read(&(context2->queuelen))) { + atomic_long_read(&(context2->queuelen))) { return xport2; } return xport1; @@ -856,7 +868,7 @@ static struct rpc_xprt *enfs_choose_shard_xport(struct rpc_xprt_switch *xps, bool found = false; struct rpc_xprt *prev = NULL; struct enfs_xprt_context *context = NULL; - struct rpc_xprt *choose_port = NULL; + struct rpc_xprt *choose_xprt = NULL; int i; int nativeLinkStatus = enfs_get_native_link_io_status(); struct route_rule rule[] = { @@ -875,7 +887,7 @@ static struct rpc_xprt *enfs_choose_shard_xport(struct rpc_xprt_switch *xps, context = (struct enfs_xprt_context *)xprt_get_reserve_context(pos); if (context == NULL - || atomic_read(&context->path_state) != PM_STATE_NORMAL) { + || !enfs_is_path_connected(atomic_read(&context->path_state))) { prev = pos; continue; } @@ -888,8 +900,7 @@ static struct rpc_xprt *enfs_choose_shard_xport(struct rpc_xprt_switch *xps, for (i = 0; i < len; i++) { if (rule[i].match(wwn, lsid, cpuId, context)) { - rule[i].xprt = - choose_less_queue_len(rule[i].xprt, pos); + rule[i].xprt = enfs_choose_better_xprt(rule[i].xprt, pos); break; } } @@ -901,9 +912,8 @@ static struct rpc_xprt *enfs_choose_shard_xport(struct rpc_xprt_switch *xps, for (i = 0; i < len; i++) { if (rule[i].match(wwn, lsid, cpuId, context)) { - rule[i].optimal_xprt = - choose_less_queue_len(rule[i].optimal_xprt, - pos); + rule[i].optimal_xprt = enfs_choose_better_xprt(rule[i].optimal_xprt, + pos); break; } } @@ -915,14 +925,14 @@ static struct rpc_xprt *enfs_choose_shard_xport(struct rpc_xprt_switch *xps, continue; if (rule[i].optimal_xprt != NULL) - choose_port = rule[i].optimal_xprt; + choose_xprt = rule[i].optimal_xprt; else - choose_port = rule[i].xprt; + choose_xprt = rule[i].xprt; break; } - return choose_port; + return choose_xprt; } static struct rpc_xprt *enfs_get_shard_xport(struct rpc_clnt *clnt, @@ -1356,8 +1366,9 @@ static int EnfsChooseNewNlmXprt(struct rpc_clnt *clnt, struct rpc_xprt *xprt, const char *local = local_name; struct sockaddr_storage srcaddr; struct rpc_xprt *nlm_xprt = (struct rpc_xprt *)data; + enum enfs_path_state state = pm_get_path_state(xprt); - if (pm_get_path_state(xprt) != PM_STATE_NORMAL) + if (!enfs_is_path_connected(state)) return 0; sockaddr_ip_to_str((struct sockaddr *)&xprt->addr, remoteip, @@ -1505,7 +1516,8 @@ static int enfs_recovery_nlm_lock(struct rpc_clnt *clnt) static int each_xprt_update_shard(struct rpc_clnt *clnt, struct rpc_xprt *xprt, void *data) { - if (pm_get_path_state(xprt) == PM_STATE_NORMAL) + enum enfs_path_state state = pm_get_path_state(xprt); + if (enfs_is_path_connected(state)) enfs_query_xprt_shard(clnt, xprt); return 0; }