-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: /admin rest api endpoint (#2094)
/admin rest api implementation and tests * open api doc * Add rest admin test to all tests * Enrich /admin get peers interface, group protocols by peers in response
- Loading branch information
1 parent
1042cac
commit 7b5c36b
Showing
11 changed files
with
517 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
{.used.} | ||
|
||
import | ||
std/sequtils, | ||
stew/shims/net, | ||
testutils/unittests, | ||
presto, presto/client as presto_client, | ||
libp2p/crypto/crypto | ||
|
||
import | ||
../../waku/waku_core, | ||
../../waku/waku_node, | ||
../../waku/node/peer_manager, | ||
../../waku/waku_api/rest/server, | ||
../../waku/waku_api/rest/client, | ||
../../waku/waku_api/rest/responses, | ||
../../waku/waku_api/rest/admin/types, | ||
../../waku/waku_api/rest/admin/handlers as admin_api, | ||
../../waku/waku_api/rest/admin/client as admin_api_client, | ||
../../waku/waku_relay, | ||
../testlib/wakucore, | ||
../testlib/wakunode, | ||
../testlib/testasync | ||
|
||
suite "Waku v2 Rest API - Admin": | ||
var node1 {.threadvar.}: WakuNode | ||
var node2 {.threadvar.}: WakuNode | ||
var node3 {.threadvar.}: WakuNode | ||
var peerInfo2 {.threadvar.}: RemotePeerInfo | ||
var peerInfo3 {.threadvar.}: RemotePeerInfo | ||
var restServer {.threadvar.}: RestServerRef | ||
var client{.threadvar.}: RestClientRef | ||
|
||
asyncSetup: | ||
node1 = newTestWakuNode(generateSecp256k1Key(), ValidIpAddress.init("127.0.0.1"), Port(60600)) | ||
node2 = newTestWakuNode(generateSecp256k1Key(), ValidIpAddress.init("127.0.0.1"), Port(60602)) | ||
peerInfo2 = node2.switch.peerInfo | ||
node3 = newTestWakuNode(generateSecp256k1Key(), ValidIpAddress.init("127.0.0.1"), Port(60604)) | ||
peerInfo3 = node3.switch.peerInfo | ||
|
||
await allFutures(node1.start(), node2.start(), node3.start()) | ||
await allFutures(node1.mountRelay(), node2.mountRelay(), node3.mountRelay()) | ||
|
||
let restPort = Port(58011) | ||
let restAddress = ValidIpAddress.init("127.0.0.1") | ||
restServer = RestServerRef.init(restAddress, restPort).tryGet() | ||
|
||
installAdminApiHandlers(restServer.router, node1) | ||
|
||
restServer.start() | ||
|
||
client = newRestHttpClient(initTAddress(restAddress, restPort)) | ||
|
||
asyncTearDown: | ||
await restServer.stop() | ||
await restServer.closeWait() | ||
await allFutures(node1.stop(), node2.stop(), node3.stop()) | ||
|
||
asyncTest "Set and get remote peers": | ||
# Connect to nodes 2 and 3 using the Admin API | ||
let postRes = await client.postPeers(@[constructMultiaddrStr(peerInfo2), | ||
constructMultiaddrStr(peerInfo3)]) | ||
|
||
check: | ||
postRes.status == 200 | ||
|
||
# Verify that newly connected peers are being managed | ||
let getRes = await client.getPeers() | ||
|
||
check: | ||
getRes.status == 200 | ||
$getRes.contentType == $MIMETYPE_JSON | ||
getRes.data.len() == 2 | ||
# Check peer 2 | ||
getRes.data.anyIt(it.protocols.find(WakuRelayCodec) >= 0 and | ||
it.multiaddr == constructMultiaddrStr(peerInfo2)) | ||
# Check peer 3 | ||
getRes.data.anyIt(it.protocols.find(WakuRelayCodec) >= 0 and | ||
it.multiaddr == constructMultiaddrStr(peerInfo3)) | ||
|
||
asyncTest "Set wrong peer": | ||
let nonExistentPeer = "/ip4/0.0.0.0/tcp/10000/p2p/16Uiu2HAm6HZZr7aToTvEBPpiys4UxajCTU97zj5v7RNR2gbniy1D" | ||
let postRes = await client.postPeers(@[nonExistentPeer]) | ||
|
||
check: | ||
postRes.status == 400 | ||
$postRes.contentType == $MIMETYPE_TEXT | ||
postRes.data == "Failed to connect to peer at index: 0 - " & nonExistentPeer | ||
|
||
# Verify that newly connected peers are being managed | ||
let getRes = await client.getPeers() | ||
|
||
check: | ||
getRes.status == 200 | ||
$getRes.contentType == $MIMETYPE_JSON | ||
getRes.data.len() == 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
when (NimMajor, NimMinor) < (1, 4): | ||
{.push raises: [Defect].} | ||
else: | ||
{.push raises: [].} | ||
|
||
import | ||
chronicles, | ||
json_serialization, | ||
json_serialization/std/options, | ||
presto/[route, client], | ||
stew/byteutils | ||
|
||
import | ||
../serdes, | ||
../responses, | ||
./types | ||
|
||
export types | ||
|
||
|
||
logScope: | ||
topics = "waku node rest admin api" | ||
|
||
proc decodeBytes*(t: typedesc[seq[WakuPeer]], data: openArray[byte], | ||
contentType: Opt[ContentTypeData]): RestResult[seq[WakuPeer]] = | ||
if MediaType.init($contentType) != MIMETYPE_JSON: | ||
error "Unsupported response contentType value", contentType = contentType | ||
return err("Unsupported response contentType") | ||
|
||
let decoded = decodeFromJsonBytes(seq[WakuPeer], data).valueOr: | ||
return err("Invalid response from server, could not decode.") | ||
|
||
return ok(decoded) | ||
|
||
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 encodeBytes*(value: seq[string], | ||
contentType: string): RestResult[seq[byte]] = | ||
if MediaType.init(contentType) != MIMETYPE_JSON: | ||
error "Unsupported contentType value", contentType = contentType | ||
return err("Unsupported contentType") | ||
|
||
let encoded = ?encodeIntoJsonBytes(value) | ||
return ok(encoded) | ||
|
||
proc getPeers*(): | ||
RestResponse[seq[WakuPeer]] | ||
{.rest, endpoint: "/admin/v1/peers", meth: HttpMethod.MethodGet.} | ||
|
||
proc postPeers*(body: seq[string]): | ||
RestResponse[string] | ||
{.rest, endpoint: "/admin/v1/peers", meth: HttpMethod.MethodPost.} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
when (NimMajor, NimMinor) < (1, 4): | ||
{.push raises: [Defect].} | ||
else: | ||
{.push raises: [].} | ||
|
||
import | ||
std/strformat, | ||
std/sequtils, | ||
stew/byteutils, | ||
chronicles, | ||
json_serialization, | ||
presto/route, | ||
libp2p/[peerinfo, switch] | ||
|
||
import | ||
../../../waku_core, | ||
../../../waku_store, | ||
../../../waku_filter, | ||
../../../waku_relay, | ||
../../../waku_node, | ||
../../../node/peer_manager, | ||
../responses, | ||
../serdes, | ||
./types | ||
|
||
export types | ||
|
||
logScope: | ||
topics = "waku node rest admin api" | ||
|
||
const ROUTE_ADMIN_V1_PEERS* = "/admin/v1/peers" | ||
|
||
type PeerProtocolTuple = tuple[multiaddr: string, protocol: string, connected: bool] | ||
|
||
func decodeRequestBody[T](contentBody: Option[ContentBody]) : Result[T, RestApiResponse] = | ||
if contentBody.isNone(): | ||
return err(RestApiResponse.badRequest("Missing content body")) | ||
|
||
let reqBodyContentType = MediaType.init($contentBody.get().contentType) | ||
if reqBodyContentType != MIMETYPE_JSON: | ||
return err(RestApiResponse.badRequest("Wrong Content-Type, expected application/json")) | ||
|
||
let reqBodyData = contentBody.get().data | ||
|
||
let requestResult = decodeFromJsonBytes(T, reqBodyData) | ||
if requestResult.isErr(): | ||
return err(RestApiResponse.badRequest("Invalid content body, could not decode. " & | ||
$requestResult.error)) | ||
|
||
return ok(requestResult.get()) | ||
|
||
proc tuplesToWakuPeers(peers: var WakuPeers, peersTup: seq[PeerProtocolTuple]) = | ||
for peer in peersTup: | ||
peers.add(peer.multiaddr, peer.protocol, peer.connected) | ||
|
||
|
||
proc installAdminV1GetPeersHandler(router: var RestRouter, node: WakuNode) = | ||
router.api(MethodGet, ROUTE_ADMIN_V1_PEERS) do () -> RestApiResponse: | ||
var peers: WakuPeers = @[] | ||
|
||
if not node.wakuRelay.isNil(): | ||
# Map managed peers to WakuPeers and add to return list | ||
let relayPeers = node.peerManager | ||
.peerStore.peers(WakuRelayCodec) | ||
.mapIt(( | ||
multiaddr: constructMultiaddrStr(it), | ||
protocol: WakuRelayCodec, | ||
connected: it.connectedness == Connectedness.Connected) | ||
) | ||
tuplesToWakuPeers(peers, relayPeers) | ||
|
||
if not node.wakuFilterLegacy.isNil(): | ||
# Map WakuFilter peers to WakuPeers and add to return list | ||
let filterPeers = node.peerManager.peerStore.peers(WakuLegacyFilterCodec) | ||
.mapIt((multiaddr: constructMultiaddrStr(it), | ||
protocol: WakuLegacyFilterCodec, | ||
connected: it.connectedness == Connectedness.Connected)) | ||
tuplesToWakuPeers(peers, filterPeers) | ||
|
||
if not node.wakuStore.isNil(): | ||
# Map WakuStore peers to WakuPeers and add to return list | ||
let storePeers = node.peerManager.peerStore | ||
.peers(WakuStoreCodec) | ||
.mapIt((multiaddr: constructMultiaddrStr(it), | ||
protocol: WakuStoreCodec, | ||
connected: it.connectedness == Connectedness.Connected)) | ||
tuplesToWakuPeers(peers, storePeers) | ||
|
||
let resp = RestApiResponse.jsonResponse(peers, status=Http200) | ||
if resp.isErr(): | ||
error "An error ocurred while building the json respose: ", error=resp.error | ||
return RestApiResponse.internalServerError(fmt("An error ocurred while building the json respose: {resp.error}")) | ||
|
||
return resp.get() | ||
|
||
proc installAdminV1PostPeersHandler(router: var RestRouter, node: WakuNode) = | ||
router.api(MethodPost, ROUTE_ADMIN_V1_PEERS) do (contentBody: Option[ContentBody]) -> RestApiResponse: | ||
|
||
let peers: seq[string] = decodeRequestBody[seq[string]](contentBody).valueOr: | ||
return RestApiResponse.badRequest(fmt("Failed to decode request: {error}")) | ||
|
||
for i, peer in peers: | ||
let peerInfo = parsePeerInfo(peer).valueOr: | ||
return RestApiResponse.badRequest(fmt("Couldn't parse remote peer info: {error}")) | ||
|
||
if not (await node.peerManager.connectRelay(peerInfo, source="rest")): | ||
return RestApiResponse.badRequest(fmt("Failed to connect to peer at index: {i} - {peer}")) | ||
|
||
return RestApiResponse.ok() | ||
|
||
proc installAdminApiHandlers*(router: var RestRouter, node: WakuNode) = | ||
installAdminV1GetPeersHandler(router, node) | ||
installAdminV1PostPeersHandler(router, node) |
Oops, something went wrong.