Skip to content

feat(portal): add unified workload diagnostics - #326

Open
Kevin Cho (chokevin) wants to merge 4 commits into
chokevin-priority-queueingfrom
chokevin-workload-diagnostics
Open

Kevin Cho (chokevin) wants to merge 4 commits into
chokevin-priority-queueingfrom
chokevin-workload-diagnostics

Conversation

@chokevin

Copy link
Copy Markdown
Contributor

What

Adds a unified Portal workload details page keyed by immutable Kubernetes resource UID. The page separates Kueue admission, Kubernetes scheduling, application readiness, and experiment tracking; provides bounded pod/container log snapshots and event evidence; and shows workload-scoped GPU utilization, temperature, power, memory, and health signals with a link to the experiment dashboard.

The overview retains the coordinated live-source refresh and priority queueing views from the lower stack layers, keeps Active jobs as a distinct runtime concept, and adds links from pending/admitted workload rows into diagnostics.

Closes #324. Incorporates the workload-scoped portion of #322 without duplicating the full experiment dashboard.

Stack

This is stack layer 3:

Why

Users need one coherent workload lifecycle story: why quota has or has not been admitted, whether Kubernetes and the application are actually running, what failure evidence exists, whether the assigned GPUs look healthy and efficient, and where to continue deeper experiment analysis. Admission is deliberately not treated as proof of execution.

Non-goals

  • Streaming logs or a general-purpose Kubernetes terminal
  • Full experiment-dashboard duplication or time-series exploration
  • Discrete XID/ECC/NVLink ingestion beyond currently available telemetry
  • Queue policy, quota, preemption, scheduling, or execution changes
  • Deployment or live-cluster mutation

Testing

  • cd portal && make test
  • cd portal && make lint
  • cd portal && make build
  • cd portal/frontend && npm test -- --run (41 tests)
  • cd portal && go test ./internal/portal/links ./internal/portalapi
  • python3 scripts/check-license-headers.py
  • git diff --check
  • Verified no unresolved conflict markers in the merged Portal source

No live cluster was contacted; end-to-end behavior is covered by repository fixtures and a session-local UI fixture rather than a deployed environment.

Risk

The main risk is correlation or authorization drift across Kueue Workloads, owner UIDs, pods, logs, telemetry, and durable history. The implementation keeps trusted workspace/namespace scope, UID-fences runtime evidence, bounds and redacts log snapshots, and explicitly marks unavailable/deleted evidence rather than guessing. Rollback is isolated to this stack layer.

AI assistance

Implementation, conflict resolution, and validation were performed with GitHub Copilot assistance.

@chokevin
Kevin Cho (chokevin) added this pull request to stack #327 September 23, 2026 03:07
if !scope.Managed && namespace == "" {
namespace = s.runs.Namespace
}
live, err := runs.Board(ctx, s.runs.Reader, runs.Options{Namespace: namespace})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When two managed workspaces share a namespace but use different LocalQueues, this lookup accepts a known workload UID from either queue. The new logs handler trusts this result, so a viewer authorized only for one workspace can read the other queue's container logs when the Portal has log access. Pass Queue: scope.LocalQueue before accepting a live workload, matching the existing runs endpoint and the Kueue fallback, and add detail/log tests for a same-namespace, different-queue workload. The queue filter already exists in runs.filterQueue.

LimitBytes: &limitBytes,
Timestamps: true,
}
data, err := c.core.Pods(namespace).GetLogs(pod, opts).DoRaw(ctx)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The default chart-managed Portal ServiceAccount cannot make this request: its ClusterRole grants reads on pods, but not the separate pods/log subresource. Every valid current/previous container-log request therefore returns a Kubernetes authorization error and becomes HTTP 502. Add a dedicated get permission for pods/log to the generated role and cover it with a chart RBAC assertion.

if (snap.objectState === 'deleted') return <Empty>Logs are not retained after the Kubernetes workload and pods are deleted.</Empty>;
if (!pod || !container) return <><Note>Choose a workload-owned container. Logs are fetched on demand as bounded snapshots and are not streamed or stored by the Portal.</Note>
{!snap.pods?.length ? <Empty>No live pods are available.</Empty> : <Table headers={['Pod', 'Container', 'State', 'Snapshots']} rows={snap.pods.flatMap(p => (p.containers || []).map(c => [
p.name, c.name, <Status value={c.state}/>, <><ScopedLink to={`?view=logs&pod=${encodeURIComponent(p.name)}&container=${encodeURIComponent(c.name)}`}>current</ScopedLink>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These query-only links navigate away from the workload page. scopedURL resolves them against window.location.origin, so ?view=logs&pod=... becomes /?view=logs&pod=...; the server then redirects to /portal instead of opening the selected logs. Prefix the current workload pathname, as Subtabs does, for the pod/container, current, previous, and "Choose another container" links. Add click-navigation coverage so the workload UID and workspace remain selected.

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.

2 participants