Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: addition of waku_api/rest/builder.nim and reduce app.nim #2623

Merged
merged 5 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions apps/wakunode2/wakunode2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import
../../waku/factory/external_config,
../../waku/factory/networks_config,
../../waku/factory/app,
../../waku/node/health_monitor
../../waku/node/health_monitor,
../../waku/waku_api/rest/builder as rest_server_builder

logScope:
topics = "wakunode main"
Expand Down Expand Up @@ -127,25 +128,31 @@ when isMainModule:
nodeHealthMonitor = WakuNodeHealthMonitor()
nodeHealthMonitor.setOverallHealth(HealthStatus.INITIALIZING)

let restServerRes = startRestServerEsentials(nodeHealthMonitor, conf)
if restServerRes.isErr():
error "Starting REST server failed.", error = $restServerRes.error()
let restServer = rest_server_builder.startRestServerEsentials(
nodeHealthMonitor, conf
).valueOr:
error "Starting esential REST server failed.", error = $error
quit(QuitFailure)

var wakunode2 = App.init(conf).valueOr:
error "App initialization failed", error = error
quit(QuitFailure)

wakunode2.restServer = restServer

nodeHealthMonitor.setNode(wakunode2.node)

wakunode2.startApp().isOkOr:
error "Starting app failed", error = error
quit(QuitFailure)

if conf.rest and not restServerRes.isErr():
wakunode2.restServer = restServerRes.value
rest_server_builder.startRestServerProtocolSupport(
restServer, wakunode2.node, wakunode2.wakuDiscv5, conf
).isOkOr:
error "Starting protocols support REST server failed.", error = $error
quit(QuitFailure)

wakunode2.setupMonitoringAndExternalInterfaces().isOkOr:
wakunode2.startMetricsServerAndLogging().isOkOr:
error "Starting monitoring and external interfaces failed", error = error
quit(QuitFailure)

Expand Down
2 changes: 1 addition & 1 deletion tests/wakunode2/test_app.nim
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ suite "Wakunode2 - App initialization":
wakunode2.startApp().isOkOr:
raiseAssert error

let mountRes = wakunode2.setupMonitoringAndExternalInterfaces()
let mountRes = wakunode2.startMetricsServerAndLogging()
assert mountRes.isOk(), mountRes.error

## Then
Expand Down
182 changes: 5 additions & 177 deletions waku/factory/app.nim
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ type
rng: ref HmacDrbgContext
key: crypto.PrivateKey

wakuDiscv5: Option[WakuDiscoveryV5]
wakuDiscv5*: Option[WakuDiscoveryV5]
dynamicBootstrapNodes: seq[RemotePeerInfo]

node: WakuNode

restServer*: Option[WakuRestServerRef]
restServer*: WakuRestServerRef
metricsServer: Option[MetricsHttpServerRef]

AppResult*[T] = Result[T, string]
Expand Down Expand Up @@ -272,172 +272,6 @@ proc startApp*(app: var App): AppResult[void] =

return ok()

## Monitoring and external interfaces

# Used to register api endpoints that are not currently installed as keys,
# values are holding error messages to be returned to the client
# NOTE: {.threadvar.} is used to make the global variable GC safe for the closure uses it
# It will always be called from main thread anyway.
# Ref: https://nim-lang.org/docs/manual.html#threads-gc-safety
var restServerNotInstalledTab {.threadvar.}: TableRef[string, string]
restServerNotInstalledTab = newTable[string, string]()

proc startRestServerEsentials*(
nodeHealthMonitor: WakuNodeHealthMonitor, conf: WakuNodeConf
): AppResult[Option[WakuRestServerRef]] =
if not conf.rest:
return ok(none(WakuRestServerRef))

let requestErrorHandler: RestRequestErrorHandler = proc(
error: RestRequestError, request: HttpRequestRef
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
try:
case error
of RestRequestError.Invalid:
return await request.respond(Http400, "Invalid request", HttpTable.init())
of RestRequestError.NotFound:
let paths = request.rawPath.split("/")
let rootPath =
if len(paths) > 1:
paths[1]
else:
""
restServerNotInstalledTab[].withValue(rootPath, errMsg):
return await request.respond(Http404, errMsg[], HttpTable.init())
do:
return await request.respond(
Http400,
"Bad request initiated. Invalid path or method used.",
HttpTable.init(),
)
of RestRequestError.InvalidContentBody:
return await request.respond(Http400, "Invalid content body", HttpTable.init())
of RestRequestError.InvalidContentType:
return await request.respond(Http400, "Invalid content type", HttpTable.init())
of RestRequestError.Unexpected:
return defaultResponse()
except HttpWriteError:
error "Failed to write response to client", error = getCurrentExceptionMsg()
discard

return defaultResponse()

let allowedOrigin =
if len(conf.restAllowOrigin) > 0:
some(conf.restAllowOrigin.join(","))
else:
none(string)

let address = conf.restAddress
let port = Port(conf.restPort + conf.portsShift)
let server =
?newRestHttpServer(
address,
port,
allowedOrigin = allowedOrigin,
requestErrorHandler = requestErrorHandler,
)

## Health REST API
installHealthApiHandler(server.router, nodeHealthMonitor)

restServerNotInstalledTab["admin"] =
"/admin endpoints are not available while initializing."
restServerNotInstalledTab["debug"] =
"/debug endpoints are not available while initializing."
restServerNotInstalledTab["relay"] =
"/relay endpoints are not available while initializing."
restServerNotInstalledTab["filter"] =
"/filter endpoints are not available while initializing."
restServerNotInstalledTab["lightpush"] =
"/lightpush endpoints are not available while initializing."
restServerNotInstalledTab["store"] =
"/store endpoints are not available while initializing."

server.start()
info "Starting REST HTTP server", url = "http://" & $address & ":" & $port & "/"

ok(some(server))

proc startRestServerProtocolSupport(app: var App): AppResult[void] =
if not app.conf.rest or app.restServer.isNone():
## Maybe we don't need rest server at all, so it is ok.
return ok()

var router = app.restServer.get().router
## Admin REST API
if app.conf.restAdmin:
installAdminApiHandlers(router, app.node)
else:
restServerNotInstalledTab["admin"] =
"/admin endpoints are not available. Please check your configuration: --rest-admin=true"

## Debug REST API
installDebugApiHandlers(router, app.node)

## Relay REST API
if app.conf.relay:
let cache = MessageCache.init(int(app.conf.restRelayCacheCapacity))

let handler = messageCacheHandler(cache)

for pubsubTopic in app.conf.pubsubTopics:
cache.pubsubSubscribe(pubsubTopic)
app.node.subscribe((kind: PubsubSub, topic: pubsubTopic), some(handler))

for contentTopic in app.conf.contentTopics:
cache.contentSubscribe(contentTopic)
app.node.subscribe((kind: ContentSub, topic: contentTopic), some(handler))

installRelayApiHandlers(router, app.node, cache)
else:
restServerNotInstalledTab["relay"] =
"/relay endpoints are not available. Please check your configuration: --relay"

## Filter REST API
if app.conf.filternode != "" and app.node.wakuFilterClient != nil:
let filterCache = MessageCache.init()

let filterDiscoHandler =
if app.wakuDiscv5.isSome():
some(defaultDiscoveryHandler(app.wakuDiscv5.get(), Filter))
else:
none(DiscoveryHandler)

rest_filter_api.installFilterRestApiHandlers(
router, app.node, filterCache, filterDiscoHandler
)
else:
restServerNotInstalledTab["filter"] =
"/filter endpoints are not available. Please check your configuration: --filternode"

## Store REST API
let storeDiscoHandler =
if app.wakuDiscv5.isSome():
some(defaultDiscoveryHandler(app.wakuDiscv5.get(), Store))
else:
none(DiscoveryHandler)

installStoreApiHandlers(router, app.node, storeDiscoHandler)

## Light push API
if app.conf.lightpushnode != "" and app.node.wakuLightpushClient != nil:
let lightDiscoHandler =
if app.wakuDiscv5.isSome():
some(defaultDiscoveryHandler(app.wakuDiscv5.get(), Lightpush))
else:
none(DiscoveryHandler)

rest_lightpush_api.installLightPushRequestHandler(
router, app.node, lightDiscoHandler
)
else:
restServerNotInstalledTab["lightpush"] =
"/lightpush endpoints are not available. Please check your configuration: --lightpushnode"

info "REST services are installed"
ok()

proc startMetricsServer(
serverIp: IpAddress, serverPort: Port
): AppResult[MetricsHttpServerRef] =
Expand All @@ -460,13 +294,7 @@ proc startMetricsLogging(): AppResult[void] =
startMetricsLog()
ok()

proc setupMonitoringAndExternalInterfaces*(app: var App): AppResult[void] =
if app.conf.rest and app.restServer.isSome():
let restProtocolSupportRes = startRestServerProtocolSupport(app)
if restProtocolSupportRes.isErr():
error "Starting REST server protocol support failed. Continuing in current state.",
error = restProtocolSupportRes.error

proc startMetricsServerAndLogging*(app: var App): AppResult[void] =
if app.conf.metricsServer:
let startMetricsServerRes = startMetricsServer(
app.conf.metricsServerAddress,
Expand All @@ -489,8 +317,8 @@ proc setupMonitoringAndExternalInterfaces*(app: var App): AppResult[void] =
# App shutdown

proc stop*(app: App): Future[void] {.async: (raises: [Exception]).} =
if app.restServer.isSome():
await app.restServer.get().stop()
if app.conf.rest:
await app.restServer.stop()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't really see where app.restServer is assigned?
So when it stops it will result in segfault:

/home/nzp/dev/status/nwaku-second/apps/wakunode2/wakunode2.nim(203) wakunode2
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/internal/asyncengine.nim(1210) runForever
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/internal/asyncengine.nim(1031) poll
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/ioselects/ioselectors_epoll.nim(643) selectInto2
../sysdeps/unix/sysv/linux/epoll_wait.c(30) epoll_wait
/home/nzp/dev/status/nwaku-second/apps/wakunode2/wakunode2.nim(175) handleCtrlC
/home/nzp/dev/status/nwaku-second/apps/wakunode2/wakunode2.nim(165) asyncStopper
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/internal/asyncfutures.nim(382) futureContinue
/home/nzp/dev/status/nwaku-second/apps/wakunode2/wakunode2.nim(166) asyncStopper
/home/nzp/dev/status/nwaku-second/waku/factory/app.nim(320) stop
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/internal/asyncfutures.nim(382) futureContinue
/home/nzp/dev/status/nwaku-second/waku/factory/app.nim(321) stop
/home/nzp/dev/status/nwaku-second/waku/waku_api/rest/server.nim(191) stop
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/internal/asyncfutures.nim(382) futureContinue
/home/nzp/dev/status/nwaku-second/waku/waku_api/rest/server.nim(193) stop
/home/nzp/dev/status/nwaku-second/apps/wakunode2/wakunode2.nim(194) handleSigsegv
Segmentation fault

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't really see where app.restServer is assigned? So when it stops it will result in segfault:

/home/nzp/dev/status/nwaku-second/apps/wakunode2/wakunode2.nim(203) wakunode2
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/internal/asyncengine.nim(1210) runForever
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/internal/asyncengine.nim(1031) poll
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/ioselects/ioselectors_epoll.nim(643) selectInto2
../sysdeps/unix/sysv/linux/epoll_wait.c(30) epoll_wait
/home/nzp/dev/status/nwaku-second/apps/wakunode2/wakunode2.nim(175) handleCtrlC
/home/nzp/dev/status/nwaku-second/apps/wakunode2/wakunode2.nim(165) asyncStopper
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/internal/asyncfutures.nim(382) futureContinue
/home/nzp/dev/status/nwaku-second/apps/wakunode2/wakunode2.nim(166) asyncStopper
/home/nzp/dev/status/nwaku-second/waku/factory/app.nim(320) stop
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/internal/asyncfutures.nim(382) futureContinue
/home/nzp/dev/status/nwaku-second/waku/factory/app.nim(321) stop
/home/nzp/dev/status/nwaku-second/waku/waku_api/rest/server.nim(191) stop
/home/nzp/dev/status/nwaku-second/vendor/nim-chronos/chronos/internal/asyncfutures.nim(382) futureContinue
/home/nzp/dev/status/nwaku-second/waku/waku_api/rest/server.nim(193) stop
/home/nzp/dev/status/nwaku-second/apps/wakunode2/wakunode2.nim(194) handleSigsegv
Segmentation fault

Wow! Thanks for it!
I tried to tackle that in d448dd6

qq: how did you force that stop ? Very interesting validation :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was easy, run with --rest=true and run node than press ctrl-c to finish.


if app.metricsServer.isSome():
await app.metricsServer.get().stop()
Expand Down
Loading
Loading