Skip to content

Read-Only State Endpoints#95

Merged
GyulyVGC merged 2 commits into
NullNet-ai:mainfrom
antoncxx:feature/http-state-endpoints
May 15, 2026
Merged

Read-Only State Endpoints#95
GyulyVGC merged 2 commits into
NullNet-ai:mainfrom
antoncxx:feature/http-state-endpoints

Conversation

@antoncxx
Copy link
Copy Markdown
Contributor

Summary

Adds five read-only HTTP endpoints to the control plane server, exposing the full in-memory state to clients over the existing port 8080 server introduced in PR 1. No new logic is introduced — this is serialisation and routing only.

Endpoints

Method Path Description
GET /api/services All services: registered status, replica addresses and ports, active session counts, proxy dependencies, trigger chains, timeout, max_networks
GET /api/nodes Connected agent nodes: IP address and list of hosted services
GET /api/pool Net ID pool: total capacity, IDs in use, IDs free
GET /api/config Raw services.toml as text/plain
GET /api/graph Live topology graph as JSON (nodes + edges)

/api/graph response shape

{
  "nodes": [
    {
      "id": "color.com",
      "registered": true,
      "entry_point": true,
      "replica_count": 1,
      "active_replica_count": 1
    }
  ],
  "edges": [
    {
      "from": "color.com",
      "to": "fs.color.com",
      "net_id": 101,
      "setup_ms": 23
    },
    {
      "from": "10.0.0.50",
      "via_proxy": "10.0.0.10",
      "to": "color.com",
      "net_id": 102,
      "setup_ms": 15
    }
  ]
}

via_proxy is omitted on service-to-service edges.

Changes

http_server — split into a module

http_server.rs is replaced by http_server/ with one file per handler:

http_server/
├── mod.rs          — AppState, serve(), route table
├── health.rs       — GET /api/health (unchanged)
├── services.rs     — GET /api/services
├── nodes.rs        — GET /api/nodes
├── pool.rs         — GET /api/pool
├── config.rs       — GET /api/config
├── graph.rs        — GET /api/graph
└── static_files.rs — SPA fallback

AppState (holding Arc<RwLock<HashMap<String, ServiceInfo>>> and Orchestrator) is passed into serve() from main.rs and threaded to handlers via axum State.

graphviz.rs

  • Extracts shared initiators() helper (previously inlined in render_graphviz)
  • Adds render_graph_json() returning a serialisable GraphJson struct; both renderers use the same active-replica logic via the shared helper

orchestrator.rs

  • connected_node_ips() — returns IPs of all nodes with an active control channel
  • pool_stats() — delegates to NetIdPool::stats()

net_id_pool.rs

  • stats() — returns (total_capacity, in_use, free) as a tuple
  • 5 new unit tests covering fresh pool state, allocation accounting, free accounting, exhaustion, and the total == in_use + free invariant

nullnet_grpc_impl.rs

  • services() and orchestrator() accessors promoted from #[cfg(test)] to always-available so main.rs can extract shared state refs before handing the struct to tonic

antoncxx added 2 commits May 14, 2026 17:13
- GET /api/services — all services with replica addresses and active session counts
- GET /api/nodes — connected agent nodes and their hosted services
- GET /api/pool — net ID pool capacity, in-use, and free counts
- GET /api/config — raw services.toml as text/plain
- GET /api/graph — live graphviz dot graph as text/plain

Supporting changes:
- NetIdPool::stats() returns (capacity, in_use, free); unit tests added
- Orchestrator::connected_node_ips() and pool_stats() expose state for HTTP layer
- NullnetGrpcImpl::services() and orchestrator() promoted from test-only
- http_server refactored into a module with one file per handler
Adds render_graph_json() alongside the existing render_graphviz().
Extracts the shared initiators() helper so both renderers use the same
active-replica logic. The handler now returns axum::Json with nodes and
edges; via_proxy is omitted on service-to-service edges.
@GyulyVGC GyulyVGC added the enhancement New feature or request label May 15, 2026
}

pub(crate) async fn connected_node_ips(&self) -> Vec<IpAddr> {
self.clients.read().await.keys().cloned().collect()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

minor, not needed to fix now but IIRC IP data structures implement Copy so no need to clone them. I'm just doing a static review so no 100% sure but this may become a .copied()

pub(crate) fn stats(&self) -> (u32, u32, u32) {
let capacity = *MAX_NET_ID - MIN_NET_ID + 1;
let in_use = (self.next_fresh - MIN_NET_ID) - self.freed.len() as u32;
(capacity, in_use, capacity - in_use)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ok for now but maybe consider computing free client-side to simplify this method's signature

.with_state(state);

let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), HTTP_PORT);
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

let's try not to use any unwrap going forward. when not possible to handle error in any way, use expect.

@GyulyVGC GyulyVGC merged commit ba7f70a into NullNet-ai:main May 15, 2026
4 checks passed
@GyulyVGC
Copy link
Copy Markdown
Collaborator

GyulyVGC commented May 15, 2026

@antoncxx merged but left some minor comments for you to later fix.

Another tip: to visually test if your graphs are correct, you can run tests against the service files I have in the test directory, and see if they look consistent (in their nodes / edges / labels representation) to the corresponding graphviz files, always in the test dir

@antoncxx antoncxx deleted the feature/http-state-endpoints branch May 15, 2026 23:43
@antoncxx antoncxx mentioned this pull request May 15, 2026
GyulyVGC pushed a commit that referenced this pull request May 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants