mirror of
https://github.com/waku-org/nwaku.git
synced 2025-01-15 17:35:45 +00:00
feat: adding filter data admin endpoint (REST) (#2314)
This commit is contained in:
parent
a34c39c891
commit
bcab9d55da
@ -1,7 +1,7 @@
|
|||||||
{.used.}
|
{.used.}
|
||||||
|
|
||||||
import
|
import
|
||||||
std/sequtils,
|
std/[sequtils,strformat],
|
||||||
stew/shims/net,
|
stew/shims/net,
|
||||||
testutils/unittests,
|
testutils/unittests,
|
||||||
presto, presto/client as presto_client,
|
presto, presto/client as presto_client,
|
||||||
@ -10,6 +10,7 @@ import
|
|||||||
import
|
import
|
||||||
../../waku/waku_core,
|
../../waku/waku_core,
|
||||||
../../waku/waku_node,
|
../../waku/waku_node,
|
||||||
|
../../waku/waku_filter_v2/client,
|
||||||
../../waku/node/peer_manager,
|
../../waku/node/peer_manager,
|
||||||
../../waku/waku_api/rest/server,
|
../../waku/waku_api/rest/server,
|
||||||
../../waku/waku_api/rest/client,
|
../../waku/waku_api/rest/client,
|
||||||
@ -26,6 +27,7 @@ suite "Waku v2 Rest API - Admin":
|
|||||||
var node1 {.threadvar.}: WakuNode
|
var node1 {.threadvar.}: WakuNode
|
||||||
var node2 {.threadvar.}: WakuNode
|
var node2 {.threadvar.}: WakuNode
|
||||||
var node3 {.threadvar.}: WakuNode
|
var node3 {.threadvar.}: WakuNode
|
||||||
|
var peerInfo1 {.threadvar.}: RemotePeerInfo
|
||||||
var peerInfo2 {.threadvar.}: RemotePeerInfo
|
var peerInfo2 {.threadvar.}: RemotePeerInfo
|
||||||
var peerInfo3 {.threadvar.}: RemotePeerInfo
|
var peerInfo3 {.threadvar.}: RemotePeerInfo
|
||||||
var restServer {.threadvar.}: RestServerRef
|
var restServer {.threadvar.}: RestServerRef
|
||||||
@ -33,6 +35,7 @@ suite "Waku v2 Rest API - Admin":
|
|||||||
|
|
||||||
asyncSetup:
|
asyncSetup:
|
||||||
node1 = newTestWakuNode(generateSecp256k1Key(), parseIpAddress("127.0.0.1"), Port(60600))
|
node1 = newTestWakuNode(generateSecp256k1Key(), parseIpAddress("127.0.0.1"), Port(60600))
|
||||||
|
peerInfo1 = node1.switch.peerInfo
|
||||||
node2 = newTestWakuNode(generateSecp256k1Key(), parseIpAddress("127.0.0.1"), Port(60602))
|
node2 = newTestWakuNode(generateSecp256k1Key(), parseIpAddress("127.0.0.1"), Port(60602))
|
||||||
peerInfo2 = node2.switch.peerInfo
|
peerInfo2 = node2.switch.peerInfo
|
||||||
node3 = newTestWakuNode(generateSecp256k1Key(), parseIpAddress("127.0.0.1"), Port(60604))
|
node3 = newTestWakuNode(generateSecp256k1Key(), parseIpAddress("127.0.0.1"), Port(60604))
|
||||||
@ -94,3 +97,58 @@ suite "Waku v2 Rest API - Admin":
|
|||||||
getRes.status == 200
|
getRes.status == 200
|
||||||
$getRes.contentType == $MIMETYPE_JSON
|
$getRes.contentType == $MIMETYPE_JSON
|
||||||
getRes.data.len() == 0
|
getRes.data.len() == 0
|
||||||
|
|
||||||
|
asyncTest "Get filter data":
|
||||||
|
await allFutures(node1.mountFilter(), node2.mountFilterClient(), node3.mountFilterClient())
|
||||||
|
|
||||||
|
let
|
||||||
|
contentFiltersNode2 = @[DefaultContentTopic, ContentTopic("2"), ContentTopic("3")]
|
||||||
|
contentFiltersNode3 = @[ContentTopic("3"), ContentTopic("4")]
|
||||||
|
pubsubTopicNode2 = DefaultPubsubTopic
|
||||||
|
pubsubTopicNode3 = PubsubTopic("/waku/2/custom-waku/proto")
|
||||||
|
|
||||||
|
expectedFilterData2 = fmt"(peerId: ""{$peerInfo2}"", filterCriteria:" &
|
||||||
|
fmt" @[(pubsubTopic: ""{pubsubTopicNode2}"", contentTopic: ""{contentFiltersNode2[0]}""), " &
|
||||||
|
fmt"(pubsubTopic: ""{pubsubTopicNode2}"", contentTopic: ""{contentFiltersNode2[1]}""), " &
|
||||||
|
fmt"(pubsubTopic: ""{pubsubTopicNode2}"", contentTopic: ""{contentFiltersNode2[2]}"")]"
|
||||||
|
|
||||||
|
expectedFilterData3 = fmt"(peerId: ""{$peerInfo3}"", filterCriteria:" &
|
||||||
|
fmt" @[(pubsubTopic: ""{pubsubTopicNode3}"", contentTopic: ""{contentFiltersNode3[0]}""), " &
|
||||||
|
fmt"(pubsubTopic: ""{pubsubTopicNode3}"", contentTopic: ""{contentFiltersNode3[1]}"")]"
|
||||||
|
|
||||||
|
let
|
||||||
|
subscribeResponse2 = await node2.wakuFilterClient.subscribe(
|
||||||
|
peerInfo1, pubsubTopicNode2, contentFiltersNode2
|
||||||
|
)
|
||||||
|
subscribeResponse3 = await node3.wakuFilterClient.subscribe(
|
||||||
|
peerInfo1, pubsubTopicNode3, contentFiltersNode3
|
||||||
|
)
|
||||||
|
|
||||||
|
assert subscribeResponse2.isOk(), $subscribeResponse2.error
|
||||||
|
assert subscribeResponse3.isOk(), $subscribeResponse3.error
|
||||||
|
|
||||||
|
let getRes = await client.getFilterSubscriptions()
|
||||||
|
|
||||||
|
check:
|
||||||
|
getRes.status == 200
|
||||||
|
$getRes.contentType == $MIMETYPE_JSON
|
||||||
|
getRes.data.len() == 2
|
||||||
|
($getRes.data).contains(expectedFilterData2)
|
||||||
|
($getRes.data).contains(expectedFilterData3)
|
||||||
|
|
||||||
|
asyncTest "Get filter data - no filter subscribers":
|
||||||
|
await node1.mountFilter()
|
||||||
|
|
||||||
|
let getRes = await client.getFilterSubscriptions()
|
||||||
|
|
||||||
|
check:
|
||||||
|
getRes.status == 200
|
||||||
|
$getRes.contentType == $MIMETYPE_JSON
|
||||||
|
getRes.data.len() == 0
|
||||||
|
|
||||||
|
asyncTest "Get filter data - filter not mounted":
|
||||||
|
let getRes = await client.getFilterSubscriptionsFilterNotMounted()
|
||||||
|
|
||||||
|
check:
|
||||||
|
getRes.status == 400
|
||||||
|
getRes.data == "Error: Filter Protocol is not mounted to the node"
|
@ -33,3 +33,11 @@ proc getPeers*():
|
|||||||
proc postPeers*(body: seq[string]):
|
proc postPeers*(body: seq[string]):
|
||||||
RestResponse[string]
|
RestResponse[string]
|
||||||
{.rest, endpoint: "/admin/v1/peers", meth: HttpMethod.MethodPost.}
|
{.rest, endpoint: "/admin/v1/peers", meth: HttpMethod.MethodPost.}
|
||||||
|
|
||||||
|
proc getFilterSubscriptions*():
|
||||||
|
RestResponse[seq[FilterSubscription]]
|
||||||
|
{.rest, endpoint: "/admin/v1/filter/subscriptions", meth: HttpMethod.MethodGet.}
|
||||||
|
|
||||||
|
proc getFilterSubscriptionsFilterNotMounted*():
|
||||||
|
RestResponse[string]
|
||||||
|
{.rest, endpoint: "/admin/v1/filter/subscriptions", meth: HttpMethod.MethodGet.}
|
||||||
|
@ -4,8 +4,7 @@ else:
|
|||||||
{.push raises: [].}
|
{.push raises: [].}
|
||||||
|
|
||||||
import
|
import
|
||||||
std/strformat,
|
std/[strformat,sequtils,sets,tables],
|
||||||
std/sequtils,
|
|
||||||
stew/byteutils,
|
stew/byteutils,
|
||||||
chronicles,
|
chronicles,
|
||||||
json_serialization,
|
json_serialization,
|
||||||
@ -32,6 +31,7 @@ logScope:
|
|||||||
topics = "waku node rest admin api"
|
topics = "waku node rest admin api"
|
||||||
|
|
||||||
const ROUTE_ADMIN_V1_PEERS* = "/admin/v1/peers"
|
const ROUTE_ADMIN_V1_PEERS* = "/admin/v1/peers"
|
||||||
|
const ROUTE_ADMIN_V1_FILTER_SUBS* = "/admin/v1/filter/subscriptions"
|
||||||
|
|
||||||
type PeerProtocolTuple = tuple[multiaddr: string, protocol: string, connected: bool]
|
type PeerProtocolTuple = tuple[multiaddr: string, protocol: string, connected: bool]
|
||||||
|
|
||||||
@ -111,6 +111,30 @@ proc installAdminV1PostPeersHandler(router: var RestRouter, node: WakuNode) =
|
|||||||
|
|
||||||
return RestApiResponse.ok()
|
return RestApiResponse.ok()
|
||||||
|
|
||||||
|
proc installAdminV1GetFilterSubsHandler(router: var RestRouter, node: WakuNode) =
|
||||||
|
router.api(MethodGet, ROUTE_ADMIN_V1_FILTER_SUBS) do () -> RestApiResponse:
|
||||||
|
|
||||||
|
if node.wakuFilter.isNil():
|
||||||
|
return RestApiResponse.badRequest("Error: Filter Protocol is not mounted to the node")
|
||||||
|
|
||||||
|
var
|
||||||
|
subscriptions: seq[FilterSubscription] = @[]
|
||||||
|
filterCriteria: seq[FilterTopic]
|
||||||
|
|
||||||
|
for (peerId, criteria) in node.wakuFilter.subscriptions.pairs():
|
||||||
|
filterCriteria = criteria.toSeq().mapIt(FilterTopic(pubsubTopic: it[0],
|
||||||
|
contentTopic: it[1]))
|
||||||
|
|
||||||
|
subscriptions.add(FilterSubscription(peerId: $peerId, filterCriteria: filterCriteria))
|
||||||
|
|
||||||
|
let resp = RestApiResponse.jsonResponse(subscriptions, 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 installAdminApiHandlers*(router: var RestRouter, node: WakuNode) =
|
proc installAdminApiHandlers*(router: var RestRouter, node: WakuNode) =
|
||||||
installAdminV1GetPeersHandler(router, node)
|
installAdminV1GetPeersHandler(router, node)
|
||||||
installAdminV1PostPeersHandler(router, node)
|
installAdminV1PostPeersHandler(router, node)
|
||||||
|
installAdminV1GetFilterSubsHandler(router, node)
|
||||||
|
@ -49,6 +49,26 @@ paths:
|
|||||||
description: Cannot connect to one or more peers.
|
description: Cannot connect to one or more peers.
|
||||||
'5XX':
|
'5XX':
|
||||||
description: Unexpected error.
|
description: Unexpected error.
|
||||||
|
/admin/v1/filter/subscriptions:
|
||||||
|
get:
|
||||||
|
summary: Get filter protocol subscribers
|
||||||
|
description: Retrieve information about the serving filter subscriptions
|
||||||
|
operationId: getFilterInfo
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Information about subscribed filter peers and topics
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/FilterSubscription'
|
||||||
|
'400':
|
||||||
|
description: Filter Protocol is not mounted to the node
|
||||||
|
'5XX':
|
||||||
|
description: Unexpected error.
|
||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
@ -72,3 +92,24 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
connected:
|
connected:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
|
FilterSubscription:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- peerId
|
||||||
|
- filterCriteria
|
||||||
|
properties:
|
||||||
|
peerId:
|
||||||
|
type: string
|
||||||
|
filterCriteria:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- pubsubTopic
|
||||||
|
- contentTopic
|
||||||
|
properties:
|
||||||
|
pubsubTopic:
|
||||||
|
type: string
|
||||||
|
contentTopic:
|
||||||
|
type: string
|
||||||
|
@ -25,6 +25,16 @@ type
|
|||||||
|
|
||||||
type WakuPeers* = seq[WakuPeer]
|
type WakuPeers* = seq[WakuPeer]
|
||||||
|
|
||||||
|
type
|
||||||
|
FilterTopic* = object
|
||||||
|
pubsubTopic*: string
|
||||||
|
contentTopic*: string
|
||||||
|
|
||||||
|
type
|
||||||
|
FilterSubscription* = object
|
||||||
|
peerId*: string
|
||||||
|
filterCriteria*: seq[FilterTopic]
|
||||||
|
|
||||||
#### Serialization and deserialization
|
#### Serialization and deserialization
|
||||||
|
|
||||||
proc writeValue*(writer: var JsonWriter[RestJson], value: ProtocolState)
|
proc writeValue*(writer: var JsonWriter[RestJson], value: ProtocolState)
|
||||||
@ -41,6 +51,20 @@ proc writeValue*(writer: var JsonWriter[RestJson], value: WakuPeer)
|
|||||||
writer.writeField("protocols", value.protocols)
|
writer.writeField("protocols", value.protocols)
|
||||||
writer.endRecord()
|
writer.endRecord()
|
||||||
|
|
||||||
|
proc writeValue*(writer: var JsonWriter[RestJson], value: FilterTopic)
|
||||||
|
{.raises: [IOError].} =
|
||||||
|
writer.beginRecord()
|
||||||
|
writer.writeField("pubsubTopic", value.pubsubTopic)
|
||||||
|
writer.writeField("contentTopic", value.contentTopic)
|
||||||
|
writer.endRecord()
|
||||||
|
|
||||||
|
proc writeValue*(writer: var JsonWriter[RestJson], value: FilterSubscription)
|
||||||
|
{.raises: [IOError].} =
|
||||||
|
writer.beginRecord()
|
||||||
|
writer.writeField("peerId", value.peerId)
|
||||||
|
writer.writeField("filterCriteria", value.filterCriteria)
|
||||||
|
writer.endRecord()
|
||||||
|
|
||||||
proc readValue*(reader: var JsonReader[RestJson], value: var ProtocolState)
|
proc readValue*(reader: var JsonReader[RestJson], value: var ProtocolState)
|
||||||
{.gcsafe, raises: [SerializationError, IOError].} =
|
{.gcsafe, raises: [SerializationError, IOError].} =
|
||||||
var
|
var
|
||||||
@ -101,6 +125,66 @@ proc readValue*(reader: var JsonReader[RestJson], value: var WakuPeer)
|
|||||||
protocols: protocols.get()
|
protocols: protocols.get()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
proc readValue*(reader: var JsonReader[RestJson], value: var FilterTopic)
|
||||||
|
{.gcsafe, raises: [SerializationError, IOError].} =
|
||||||
|
var
|
||||||
|
pubsubTopic: Option[string]
|
||||||
|
contentTopic: Option[string]
|
||||||
|
|
||||||
|
for fieldName in readObjectFields(reader):
|
||||||
|
case fieldName
|
||||||
|
of "pubsubTopic":
|
||||||
|
if pubsubTopic.isSome():
|
||||||
|
reader.raiseUnexpectedField("Multiple `pubsubTopic` fields found", "FilterTopic")
|
||||||
|
pubsubTopic = some(reader.readValue(string))
|
||||||
|
of "contentTopic":
|
||||||
|
if contentTopic.isSome():
|
||||||
|
reader.raiseUnexpectedField("Multiple `contentTopic` fields found", "FilterTopic")
|
||||||
|
contentTopic = some(reader.readValue(string))
|
||||||
|
else:
|
||||||
|
unrecognizedFieldWarning()
|
||||||
|
|
||||||
|
if pubsubTopic.isNone():
|
||||||
|
reader.raiseUnexpectedValue("Field `pubsubTopic` is missing")
|
||||||
|
|
||||||
|
if contentTopic.isNone():
|
||||||
|
reader.raiseUnexpectedValue("Field `contentTopic` are missing")
|
||||||
|
|
||||||
|
value = FilterTopic(
|
||||||
|
pubsubTopic: pubsubTopic.get(),
|
||||||
|
contentTopic: contentTopic.get()
|
||||||
|
)
|
||||||
|
|
||||||
|
proc readValue*(reader: var JsonReader[RestJson], value: var FilterSubscription)
|
||||||
|
{.gcsafe, raises: [SerializationError, IOError].} =
|
||||||
|
var
|
||||||
|
peerId: Option[string]
|
||||||
|
filterCriteria: Option[seq[FilterTopic]]
|
||||||
|
|
||||||
|
for fieldName in readObjectFields(reader):
|
||||||
|
case fieldName
|
||||||
|
of "peerId":
|
||||||
|
if peerId.isSome():
|
||||||
|
reader.raiseUnexpectedField("Multiple `peerId` fields found", "FilterSubscription")
|
||||||
|
peerId = some(reader.readValue(string))
|
||||||
|
of "filterCriteria":
|
||||||
|
if filterCriteria.isSome():
|
||||||
|
reader.raiseUnexpectedField("Multiple `filterCriteria` fields found", "FilterSubscription")
|
||||||
|
filterCriteria = some(reader.readValue(seq[FilterTopic]))
|
||||||
|
else:
|
||||||
|
unrecognizedFieldWarning()
|
||||||
|
|
||||||
|
if peerId.isNone():
|
||||||
|
reader.raiseUnexpectedValue("Field `peerId` is missing")
|
||||||
|
|
||||||
|
if filterCriteria.isNone():
|
||||||
|
reader.raiseUnexpectedValue("Field `filterCriteria` are missing")
|
||||||
|
|
||||||
|
value = FilterSubscription(
|
||||||
|
peerId: peerId.get(),
|
||||||
|
filterCriteria: filterCriteria.get()
|
||||||
|
)
|
||||||
|
|
||||||
## Utility for populating WakuPeers and ProtocolState
|
## Utility for populating WakuPeers and ProtocolState
|
||||||
func `==`*(a, b: ProtocolState): bool {.inline.} =
|
func `==`*(a, b: ProtocolState): bool {.inline.} =
|
||||||
return a.protocol == b.protocol
|
return a.protocol == b.protocol
|
||||||
|
Loading…
x
Reference in New Issue
Block a user