docs.fermihdi.io — graphql_services.md

/ › hd › 2.0.5 › md

Download graphql_services.mdView raw

The GraphQL Services

Two GraphQL front doors sit on the HD Proxy. One is generic: its schema is generated from every dataset every Proxy it knows serves. The other is purpose-built for the network-flow dataset and speaks in the shapes a dashboard wants — time series, top-N, aggregates. Both translate a GraphQL query into the HD query underneath, receive the records directly from the Storage Nodes, and evaluate the rest locally with the SDK's engine. The rules they share with every other door are on the querying page.

The generic service: hd_graphql

The service reads a proxy list and builds its schema from what each Proxy's /v1/hd/schema returns, watching the file for changes:

# proxies.yaml
proxies:
  prod:
    url: https://proxy.hd.internal:6986

Every dataset of every Proxy becomes a query field, <proxy>.<dataset>, with an object type carrying the dataset's fields (integers as Int, floats as Float, booleans, everything else as String) and an enum of field names for filters. The argument is one queryElements object:

Argument
startTime, endTime the window, in Unix seconds. Given either, the batches for that window are named; given neither, the whole dataset is read — the service acknowledges the full scan on the caller's behalf, because it has no interface of its own to ask through
filters groups of {fieldName, op, value}, AND within a group and OR between groups, op one of EQUALITY, NOT_EQUALITY, GREATER, LESS. These are what the Storage Nodes evaluate, except a condition on a time stored as a delta from its batch, which is applied to the fetched records
queryQos priority weight 1–255
query {
  prod {
    ipflow(queryElements: {
      startTime: 1788660000, endTime: 1788663600,
      filters: [{ andFilters: [{ fieldName: ip_src, op: EQUALITY, value: "10.0.0.1" }] }]
    }) { timestamp ip_src ip_dst port_dst bytes }
  }
}

Three mutations are escape hatches for a client that wants the SDK's engines directly on the records already fetched: executeRawSQL, executeRawGraph and executeRawVector, each taking a proxy and a query string and returning the engine's answer as text.

An address literal in a filter is converted to the integer it is before the Storage Nodes see it, and an address column is rendered as an address in the result. healthCheck is the liveness field. The service listens on GRAPHQL_LISTEN_PORT (8083 by default) and reads PROXY_CONFIG_PATH; a GraphiQL page can be served beside it. It receives results on FERMIHDI_DATA_RETURN_PORT (6987) at the address it discovers inside FERMIHDI_DATA_PLANE_SUBNET, or at FERMIHDI_DATA_RETURN_HOST when discovery cannot pick the right interface.

The IP-flow service: hd_ipflow_graphql

One dataset, three query fields, at POST /query on HD_SERVER_PORT (8080):

Field Returns
getFlowsTS(queryElements, paging) the matching flows, or their aggregates, as a time series
getFlowsTopXTS(queryElements, topXElements, paging) the top N by the ordering given, as a time series — for a "top talkers" panel
getScalers(queryElements, dataPrepFilters) single figures over the selection: the maximum, the total

queryElements carries the whole question:

Part
when fromDateTime and toDateTime, each as an ISO string, Unix seconds or milliseconds. Both ends
filters groups of andFilters, OR-ed together; each {fieldName, type, value} with type EQUALITY, GRATER or LESS. A value written as an address is converted for you
aggregates {fieldName, function, returnType} — SUM, and the rest of the aggregate set — computed after the records arrive
groupBy field names to group the aggregates by
rollUp {intervalMs}: the time-series bucket width
timeoutMs, queryQos the wait, and the priority weight

paging is {first, skip} and is honoured: the page asked for is the page returned. Address fields — ipSrc, ipDst, peerIpSrc — come back as addresses, an IPv4-mapped value as its dotted IPv4.

query TopTalkers($from: Float!, $to: Float!, $interval: Float!) {
  getFlowsTS(queryElements: {
    when: { fromDateTime: { fromDateTimeMs: $from }, toDateTime: { toDateTimeMs: $to } },
    filters: [{ andFilters: [{ fieldName: asSrc, type: GRATER, value: "0" }] }],
    aggregates: [{ fieldName: bytes, function: SUM, returnType: uint64 },
                 { fieldName: packets, function: SUM, returnType: uint64 }],
    groupBy: [asSrc, asDst, ipSrc, ipDst],
    rollUp: { intervalMs: $interval }
  }) { timestampMS asSrc asDst ipSrc ipDst bytes packets bitsPerSecond packetsPerSecond }
}

The service is configured by HD_-prefixed environment variables or a JSON file: HD_FERMIHDI_HD_PROXY_ENDPOINT is the Proxy, and HD_SERVER_AUTH_ENABLED with HD_SERVER_API_TOKEN_SECRET turns on bearer-token checking for a deployment that does not put it behind the authenticating front door. It shuts down cleanly on POST /v1/shutdown, draining in-flight queries first. Its data-return settings are the same three as the generic service's.

Placing either

Both are engine roles in the installer's terms — they carry the SDK, so they want 2 MB hugepages and pinned cores like the other engine roles, and the Administrator's Guide sizes them. Both advertise a data-return address to the Storage Nodes and both fail silently when it is wrong: the query is accepted, the results go to an address nobody is listening on, and the caller waits. The operators manual walks through choosing it.

Download graphql_services.mdView raw