From d7cd41d78ce0cae985f96dd400a7db4dbc32e9bd Mon Sep 17 00:00:00 2001 From: NagyZoltanPeter <113987313+NagyZoltanPeter@users.noreply.github.com> Date: Fri, 8 Sep 2023 11:19:47 +0200 Subject: [PATCH] feat: Rest endoint /health for rln (#2011) * Rest endoint /health added * Remove dev-debug echo * Changed not ready message * Update waku/node/rest/health/handlers.nim Co-authored-by: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com> * Various fixes on PR foundings, added comments --------- Co-authored-by: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com> --- apps/wakunode2/app.nim | 4 ++ tests/all_tests_waku.nim | 3 +- tests/wakunode_rest/test_rest_health.nim | 77 ++++++++++++++++++++++++ waku/node/rest/health/client.nim | 30 +++++++++ waku/node/rest/health/handlers.nim | 42 +++++++++++++ waku/node/rest/health/openapi.yaml | 41 +++++++++++++ 6 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 tests/wakunode_rest/test_rest_health.nim create mode 100644 waku/node/rest/health/client.nim create mode 100644 waku/node/rest/health/handlers.nim create mode 100644 waku/node/rest/health/openapi.yaml diff --git a/apps/wakunode2/app.nim b/apps/wakunode2/app.nim index e72c474d4..5dfbc1fe3 100644 --- a/apps/wakunode2/app.nim +++ b/apps/wakunode2/app.nim @@ -47,6 +47,7 @@ import ../../waku/node/rest/relay/topic_cache, ../../waku/node/rest/filter/handlers as rest_filter_api, ../../waku/node/rest/store/handlers as rest_store_api, + ../../waku/node/rest/health/handlers as rest_health_api, ../../waku/node/jsonrpc/admin/handlers as rpc_admin_api, ../../waku/node/jsonrpc/debug/handlers as rpc_debug_api, ../../waku/node/jsonrpc/filter/handlers as rpc_filter_api, @@ -569,6 +570,9 @@ proc startRestServer(app: App, address: ValidIpAddress, port: Port, conf: WakuNo ## Debug REST API installDebugApiHandlers(server.router, app.node) + ## Health REST API + installHealthApiHandler(server.router, app.node) + ## Relay REST API if conf.relay: let relayCache = TopicCache.init(capacity=conf.restRelayCacheCapacity) diff --git a/tests/all_tests_waku.nim b/tests/all_tests_waku.nim index 357bfdc23..d5ed75052 100644 --- a/tests/all_tests_waku.nim +++ b/tests/all_tests_waku.nim @@ -102,4 +102,5 @@ when defined(rln): ./waku_rln_relay/test_waku_rln_relay, ./waku_rln_relay/test_wakunode_rln_relay, ./waku_rln_relay/test_rln_group_manager_onchain, - ./waku_rln_relay/test_rln_group_manager_static + ./waku_rln_relay/test_rln_group_manager_static, + ./wakunode_rest/test_rest_health diff --git a/tests/wakunode_rest/test_rest_health.nim b/tests/wakunode_rest/test_rest_health.nim new file mode 100644 index 000000000..46b2ea75b --- /dev/null +++ b/tests/wakunode_rest/test_rest_health.nim @@ -0,0 +1,77 @@ +{.used.} + +import + std/tempfiles, + stew/shims/net, + testutils/unittests, + presto, + presto/client as presto_client, + libp2p/peerinfo, + libp2p/multiaddress, + libp2p/crypto/crypto +import + ../../waku/waku_node, + ../../waku/node/waku_node as waku_node2, # TODO: Remove after moving `git_version` to the app code. + ../../waku/node/rest/server, + ../../waku/node/rest/client, + ../../waku/node/rest/responses, + ../../waku/node/rest/health/handlers as health_api, + ../../waku/node/rest/health/client as health_api_client, + ../../waku/waku_rln_relay, + ../testlib/common, + ../testlib/wakucore, + ../testlib/wakunode + + +proc testWakuNode(): WakuNode = + let + privkey = crypto.PrivateKey.random(Secp256k1, rng[]).tryGet() + bindIp = ValidIpAddress.init("0.0.0.0") + extIp = ValidIpAddress.init("127.0.0.1") + port = Port(0) + + newTestWakuNode(privkey, bindIp, port, some(extIp), some(port)) + + +suite "Waku v2 REST API - health": + asyncTest "Get node health info - GET /health": + # Given + let node = testWakuNode() + await node.start() + await node.mountRelay() + + let restPort = Port(58001) + let restAddress = ValidIpAddress.init("0.0.0.0") + let restServer = RestServerRef.init(restAddress, restPort).tryGet() + + installHealthApiHandler(restServer.router, node) + restServer.start() + let client = newRestHttpClient(initTAddress(restAddress, restPort)) + + # When + var response = await client.healthCheck() + + # Then + check: + response.status == 503 + $response.contentType == $MIMETYPE_TEXT + response.data == "Node is not ready" + + # now kick in rln (currently the only check for health) + await node.mountRlnRelay(WakuRlnConfig(rlnRelayDynamic: false, + rlnRelayCredIndex: some(1.uint), + rlnRelayTreePath: genTempPath("rln_tree", "wakunode"), + )) + + # When + response = await client.healthCheck() + + # Then + check: + response.status == 200 + $response.contentType == $MIMETYPE_TEXT + response.data == "Node is healthy" + + await restServer.stop() + await restServer.closeWait() + await node.stop() diff --git a/waku/node/rest/health/client.nim b/waku/node/rest/health/client.nim new file mode 100644 index 000000000..c1a0ccfe6 --- /dev/null +++ b/waku/node/rest/health/client.nim @@ -0,0 +1,30 @@ +when (NimMajor, NimMinor) < (1, 4): + {.push raises: [Defect].} +else: + {.push raises: [].} + +import + chronicles, + json_serialization, + json_serialization/std/options, + presto/[route, client] +import + ../serdes, + ../responses + +logScope: + topics = "waku node rest health_api" + +proc decodeBytes*(t: typedesc[string], value: openArray[byte], + contentType: Opt[ContentTypeData]): RestResult[string] = + if MediaType.init($contentType) != MIMETYPE_TEXT: + error "Unsupported contentType value", contentType = contentType + return err("Unsupported contentType") + + var res: string + if len(value) > 0: + res = newString(len(value)) + copyMem(addr res[0], unsafeAddr value[0], len(value)) + return ok(res) + +proc healthCheck*(): RestResponse[string] {.rest, endpoint: "/health", meth: HttpMethod.MethodGet.} diff --git a/waku/node/rest/health/handlers.nim b/waku/node/rest/health/handlers.nim new file mode 100644 index 000000000..9b4fffdfe --- /dev/null +++ b/waku/node/rest/health/handlers.nim @@ -0,0 +1,42 @@ +when (NimMajor, NimMinor) < (1, 4): + {.push raises: [Defect].} +else: + {.push raises: [].} + +import + chronicles, + json_serialization, + presto/route +import + ../../waku_node, + ../responses, + ../serdes + +logScope: + topics = "waku node rest health_api" + +const ROUTE_HEALTH* = "/health" + +const FutIsReadyTimout = 5.seconds + +proc installHealthApiHandler*(router: var RestRouter, node: WakuNode) = + ## /health endpoint provides information about node readiness to caller. + ## Currently it is restricted to checking RLN (if mounted) proper setup + ## TODO: Leter to extend it to a broader information about each subsystem state + ## report. Rest response to change to JSON structure that can hold exact detailed + ## information. + + router.api(MethodGet, ROUTE_HEALTH) do () -> RestApiResponse: + + let isReadyStateFut = node.isReady() + if not await isReadyStateFut.withTimeout(FutIsReadyTimout): + return RestApiResponse.internalServerError("Health check timed out") + + var msg = "Node is healthy" + var status = Http200 + + if not isReadyStateFut.read(): + msg = "Node is not ready" + status = Http503 + + return RestApiResponse.textResponse(msg, status) diff --git a/waku/node/rest/health/openapi.yaml b/waku/node/rest/health/openapi.yaml new file mode 100644 index 000000000..f433800c2 --- /dev/null +++ b/waku/node/rest/health/openapi.yaml @@ -0,0 +1,41 @@ +openapi: 3.0.3 +info: + title: Waku V2 node REST API + version: 1.0.0 + contact: + name: VAC Team + url: https://forum.vac.dev/ + +tags: + - name: health + description: Healt check REST API for WakuV2 node + +paths: + /health: + get: + summary: Get node health status + description: Retrieve readiness of a Waku v2 node. + operationId: healthcheck + tags: + - health + responses: + '200': + description: Waku v2 node is up and running. + content: + text/plain: + schema: + type: string + example: Node is healty + '500': + description: Internal server error + content: + text/plain: + schema: + type: string + '503': + description: Node not initialized or having issues + content: + text/plain: + schema: + type: string + example: Node is not initialized