Skip to content

feat(vmm-top): terminal dashboard and read-only hub for vmm nodes - #1457

Draft
Leechael wants to merge 5 commits into
nextfrom
claude/vmm-tui-server-client-ay9j3a
Draft

Leechael wants to merge 5 commits into
nextfrom
claude/vmm-tui-server-client-ay9j3a

Conversation

@Leechael

Copy link
Copy Markdown
Collaborator

Problem

Watching a dstack fleet today means opening each node's web console separately (:9080 per VMM) or running vmm-cli.py lsvm against one node at a time. Nothing shows several nodes on one screen, flags which VMs need attention across them, or keeps a record of state changes while you watch.

Most VMMs also listen only on a unix socket (address = "unix:./vmm.sock" by default), each node has its own [auth] token, and every operator polling every node multiplies the load. So "just poll them all from my laptop" doesn't work out of the box.

Fix

A new standalone binary, dstack-vmm-top (dstack/vmm-top/), built with ratatui. It is only ever a client of the VMM's existing API (Status, Version, GetMeta, ListGpus, StartVm/ShutdownVm/StopVm, /logs), so dstack-vmm itself is unchanged.

It watches sources of two kinds, in any mix:

  • A VMM: -n tdx-01=http://10.0.0.11:9080, unix:/path/vmm.sock, or auto-discovered from the registration files every dstack-vmm writes (vmm/src/discovery.rs).
  • A hub: dstack-vmm-top serve polls many VMMs (including local unix-socket ones), then serves them under one URL and one token. Watch it with --hub https://host:9970.

The hub is read-only by construction. It has GET routes only, no route that changes a VM, and snapshots exclude encrypted env, user config and the compose file. A token is required on non-loopback addresses unless --insecure is passed. Log requests are accepted only for VM ids the node itself listed. Lifecycle actions are offered only for nodes polled directly with their own VMM token.

The dashboard:

  • Tabs: Fleet (node → VM tree with vCPU, memory, disk and GPU allocation against node limits), VMs (flat and sortable across nodes), and Events (state changes, node outages, actions, guest boot events).
  • Detail view: Info, Serial, Stdout, Stderr and Events, with logs following the tail.
  • Navigation: filter (/), scope all / live / attention (a), preview pane (v), and mouse support.
  • Actions: start, shutdown, restart and force stop, each behind a confirmation (x). The VMM has no restart call, so restart is stop then start. That sequence is safe because start_vm waits for a VM that was told to stop to finish exiting (vmm/src/app.rs).
  • Timeouts: actions get their own 120s bound. The VMM can take up to 15s to stop, 30s for a guest shutdown, and 60s for a start that waits on an exit, so reusing the 8s poll bound would report slow successes as failures.
  • Non-interactive output: --once / --json, also used when stdout is not a terminal.

Alternatives considered

  • dstack vmm top subcommand of the existing CLI: one less binary, but every dstack build would then link ratatui and crossterm.
  • Separate repo: independently releasable, but it would drift from the VMM API, which this crate gets for free through dstack-vmm-rpc.
  • Deploying and updating VMs from the TUI: dropped on purpose. It needs app-compose construction and client-side env encryption ported to Rust, which deserves its own change.

Commits

Each commit builds and passes tests and clippy on its own:

  1. build(deps): workspace deps only.
  2. feat(vmm-top): read-only dashboard plus --once / --json.
  3. feat(vmm-top): VM lifecycle actions. This is the only write path, so it can be reviewed alone.
  4. feat(vmm-top): the hub, server and client. This is a new network surface, so it can be reviewed alone.
  5. docs(vmm-top): the guide.

Verification

  • cargo test -p dstack-vmm-top: 40 unit tests covering phase folding, tree, sort and scope, selection that stays on the same VM across polls, journal diffing, pause, log follow, layout at 20×5 up to 200×60, column widths at every width from 30 to 220, ANSI and OSC stripping, hub query parsing and token comparison.
  • cargo clippy -p dstack-vmm-top -- -D warnings -D clippy::expect_used -D clippy::unwrap_used (CI's flags) and cargo fmt --check --all are clean at every commit.
  • End to end against mock VMMs that implement the same pRPC JSON API:
    • Two nodes polled directly, one of them down, plus a hub serving two more. One hub node had [auth] enabled.
    • Confirmed: the hub refuses 0.0.0.0 without a token, answers 401 without a token and 405 to POST, and returns no VM ../../etc for an id the node didn't list.
    • Restart went through while the mock's StopVm blocked for 10s.
  • Not run against a real dstack-vmm: it would not start in the test container because its host API needs a vsock device. Field names are shared by construction through the generated dstack-vmm-rpc types, and the start/stop calls are the same VmmClient<PrpcClient> calls the dstack CLI already makes.

Notes for review

  • Cargo.lock: adding any new crates.io dependency (even unicode-width alone) makes Cargo move hyper-util, quinn-udp and one other crate from socket2 0.5.10 to 0.6.4, which is already in the lockfile. I checked this against a clean worktree. It is not caused by any specific dependency here.
  • Not covered by this PR: vmm/ui/package-lock.json resolves from registry.npmmirror.com. Behind proxies that block that host, npm ci fails, and with it the dstack-vmm build.

Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant