Skip to main content

Workload

Overview

This document covers the Workload screen (gpuWorkload / route name Workload) — a filterable table of all Volcano PodGroup/workload objects across clusters, with drill-down to Workload Detail (topology canvas) and Workload Log (log viewer), plus a “Deploy Workload” creation modal (guide-only, links out to Model Manager). It is one of the three screens under the “Workload Management” LNB menu group (gpuWorkloadGrp — routes.manifest.json:305-312), alongside Scheduling (see 1-scheduling.mdx) and Time Table (see 2-timetable.mdx).

Workload lives under src/modules/scheduler/dashboard/ (the “scheduler(volcano) 병합 모듈” — a merged Volcano-scheduler feature set), and is wired into the host router via src/pages/index.ts + src/router/route-defs.ts + routes.manifest.json. Both a system (/system/gpu/...) and a project (/proj/{project}/gpu/...) variant exist, sharing the same Vue components.

Route Design

Route names/constants: src/constants/routeNames/index.ts, paths: src/constants/routes/index.ts.

ScreenRoute name (system)Route name (project)PathComponent (lazy import)
Workload (list)Workload (SCHEDULER_WORKLOAD)projectGpuSchedulerWorkload (PROJECT_SCHEDULER_WORKLOAD)/workload → /system/gpu/workload / /proj/{project}/gpu/workload@/modules/scheduler/dashboard/pages/Workload/index.vue — src/pages/index.ts:51-52
Workload Detail (hidden drill-down)WorkloadDetail (SCHEDULER_WORKLOAD_DETAIL)same/workload/detail/:cluster/:namespace/:name@/modules/scheduler/dashboard/pages/WorkloadDetail/index.vue — src/pages/index.ts:55-56
Workload Log (hidden drill-down)WorkloadLog (SCHEDULER_WORKLOAD_LOG)same/workload/logs/:poolName/:workloadName@/modules/scheduler/dashboard/pages/WorkloadLog/index.vue — src/pages/index.ts:49-50

Route registration/mapping: src/router/index.ts:36-52 (system componentMap) and :71-86 (project componentMap). Route metadata (title/icon/parent/apis) is declared in src/router/route-defs.ts:620-643 (Workload def) and mirrored/generated into routes.manifest.json:753-859 (gpuWorkload menu entry) plus :1323-1434 for the project-mode projectGpuSchedulerWorkload entry. An old alias redirect exists for the legacy path (/scheduler/workload → SCHEDULER_WORKLOAD) — src/constants/routes/index.ts:78.

ROUTE_NAMES.SCHEDULER_WORKLOAD_DETAIL/_LOG are declared once and reused by both system and project route tables (route-defs.ts:660-668, 683-692 and :851-869), so the same page component serves both contexts; project scoping is done inside the page via getHostProjectId() rather than by separate components.

UI Structure

Workload Management (LNB group, order=3)
├── Scheduling (order 1) → see 1-scheduling.mdx
├── Time Table (order 2) → see 2-timetable.mdx
└── Workload (order 3) → Workload/index.vue ← THIS DOC
├── Workload Detail (hidden) → WorkloadDetail/index.vue (VueFlow topology canvas)
└── Workload Log (hidden) → WorkloadLog/index.vue (log viewer, split layout)

Workload page (Workload/index.vue)

  • Header with title + “Deploy Workload” button opening a 2-step creation modal (:2-16, modal :284-447).
  • Filter bar: Cluster / Project / GPU Pool / Workload Type / GPU Type / State selects, plus a “search in

” + text search row, and active-filter tag chips (:18-122).

  • Loading / error / empty states (:124-147).
  • Results shown as <Table> (from @cloudz-mp/zmp-common-ui) with accordion sub-rows revealing active_pods (:143-282).
  • Columns: Workload(name), GPU Health, Type, Category, Priority, Preemptible, Status, GPU Pool, GPU Allocation, CPU/Memory, Created At, Project, Cluster, Actions (tableColumns, :861-889).
  • Row actions via WorkloadActionMenu (stop/delete/detail) — :229-239.
  • GPU health badge (“Action needed”) that deep-links into GPU Fault screen — :186-206, openFaultInInventory :850-859.

Workload Detail page (WorkloadDetail/index.vue)

  • Breadcrumb + live status header (heart/warn icon, “Priority Run” button gated by ownership + edit permission) — :1-59.
  • VueFlow canvas rendering a Workload node and Pod nodes with edges — :76-150+.
  • PriorityRunDialog reused for ad-hoc priority elevation.

Workload Log page (WorkloadLog/index.vue)

  • Breadcrumb (GPU Overview → Pool → Workload name), title bar with streaming tag, download-logs and back-to-pool icon buttons — :1-89.
  • Split layout: left info sidebar (Status / Spec / Resource-per-pod sections) + (below the read excerpt) a main log-stream panel — :91-150+.

Component Design

Key components used by these three screens (all under src/modules/scheduler/dashboard/components/ unless noted):

ComponentFileUsed byProps/Emits (from usage)
ClusterSelectGuardcomponents/ClusterSelectGuard.vueWorkload/index.vue, WorkloadDetail/index.vue, WorkloadLog/index.vuenone — self-contained guard that blocks pages until a cluster is chosen when the realm is multi-cluster
WorkloadActionMenucomponents/WorkloadActionMenu/index.vueWorkload/index.vue (row actions)props: name, cluster, project, namespace, owner-kind; emits: done, detail. Logic in sibling action.ts (resolveStopAction)
PriorityRunDialogcomponents/PriorityRunDialog/index.vueWorkloadDetail/index.vueprops: open, workload-name, subtitle, status-badge, footnote, target-rect, gang-info; emits: close, pick
BackChipsrc/modules/scheduler/common/BackChip.vueWorkloadDetail/index.vue:fallback

Composables consumed:

ComposableFileUsed byPurpose
useFaultStatesrc/modules/scheduler/fault/useFaultState.tsWorkload/index.vue (:469,831)Shared GPU-fault/alert lookup so the Workload table’s “Action needed” badge matches the Inventory tree’s badge logic
useProfileStore (Pinia)src/modules/scheduler/_host/useProfileStore.tsWorkload/index.vueRealm/cluster/namespace/project selection state shared across the whole scheduler module
getHostProjectIdsrc/modules/scheduler/_host/utils/hostContext.tsAll three pagesDetects project-mode (/proj/{id}/...) to lock filters/scope
workloadHelperspages/Workload/workloadHelpers.tsWorkload/index.vueworkloadTypeOf/Label, gpuTypeOf/Label, gpuResourceText, formatDateTime, relativeAge — pure derivation helpers for table cells

API Design

All Volcano-scheduler API calls are centralized in src/modules/scheduler/dashboard/apis/index.ts (base path /core/v1/scheduler, axios instance = services.axiosInstance from _host/baseUi.ts). GPU-pool “core” endpoints (a separate /core/v1/realms/{realm}/gpu/pools) live in src/modules/scheduler/dashboard/apis/coreGpu.ts (fetchCoreGpuPools, used by these pages to scope by project pool ownership).

FunctionMethod & PathUsed by (file:line)Notes
fetchWorkloads(namespace, gpuPool?, cluster?, project?)GET {realm-cluster}/workloadsWorkload/index.vue:459,591,604Normalizes 2 possible BE response shapes via normalizeWorkload() (apis/index.ts:184-276). Auto-injects project from getHostProjectId() if caller omits it (apis/index.ts:328).
elevateWorkloadPriority(namespace, podName, priority, cluster?)POST \{realm-cluster\}/actions/direct-runPriority-run dialogs (WorkloadDetail)priority 100–900 (P1–P9)
evictWorkload(namespace, podName, cluster?)POST /core/v1/scheduler/clusters/\{cluster\}/actions/evictWorkloadActionMenu/action.tsServer dispatches to stop-isvc (KServe) or delete depending on owner kind
fetchGpuPoolList(projectId?)derives from fetchVolcanoQueues() → GET {realm-cluster}/volcano-queuesWorkload/index.vue:462,666Maps queue name to {label, value}
fetchProjects()GET /core/v1/realms/{realm}/projectsWorkload/index.vue:461,654Filter-bar project options
fetchCoreGpuPools()GET /core/v1/realms/{realm}/gpu/poolsapis/coreGpu.ts; all three pagesPool ownership ledger, used to scope “my project’s pools”
fetchPodLogs(cluster, namespace, appId, podName, tailLines)GET {realm-cluster}/applications/{appId}/logsWorkloadLog page (log tail)
fetchWorkloadManifest(cluster, namespace, kind, name)GET {realm-cluster}/workloads/manifestWorkloadDetail (YAML drawer)
resolveCluster() / resolveRealm()n/a (client helper)All three pagesSee Business Logic — cluster resolution/guard rules

Route-level API permission bindings for this screen (used by the host’s manifest-based RBAC, DERIVED_ONLY mode) are declared in the schedulerWorkloadApis constant — src/router/route-defs.ts:240-260 — and mirrored in routes.manifest.json:812-858 (gpuWorkload). Key mutating endpoints requiring edit verb: POST .../actions/direct-run-internal, POST .../actions/evict.

State Management

  • Pinia store: useProfileStore (src/modules/scheduler/_host/useProfileStore.ts, store id from STORES.PROFILE, defined with the Composition API defineStore setup syntax). Holds realm/cluster list (clusters, schedulerClusters), selectedCluster, selectedNamespace, selectedProject, projects, namespaces, and ensureProfile() to lazy-load the profile. The Workload page reads/writes this store for scope filters (Workload/index.vue:494,510,574-576,624-629).
  • Other Pinia stores in the module (not primary to this screen but part of the same scheduler dashboard state layer): useQueueStore (stores/useQueueStore/index.ts, built from generic createQueueStore(STORES.QUEUE_DASHBOARD) factory in stores/createQueueStore.ts), useSystemQueueStore, useGpuPoolAdminStore.
  • Local component state (no store): the Workload page otherwise uses plain ref/reactive/computed local to the SFC — no dedicated Pinia store for the page. It keeps workloads, filters (reactive), projectOptions, poolOptions, createOpen/form (deploy modal) as page-local state (Workload/index.vue:486-569,552-553,1009-1023).
  • Cross-page cache: none observed — each page fetches independently on mount/poll; no shared workload cache/store between Workload list and Time Table (confirmed by both calling fetchWorkloads separately with their own params).

Business Logic

  1. Cluster resolution / guard rule — resolveCluster() (apis/index.ts:67-83): explicit selection (?cluster= query or store.selectedCluster) wins; if realm has exactly 1 cluster it auto-selects; if it has >1 clusters and none explicitly chosen it throws (“클러스터를 먼저 선택해 주세요.”) rather than silently defaulting to clusters[0] — enforced via ClusterSelectGuard component on Workload/WorkloadDetail/ WorkloadLog pages. The Workload list additionally supports fanning out across all clusters via an explicit “All” selection rather than requiring single-cluster selection up front (Workload/index.vue:573-580).
  2. Project isolation (“설계 ④”) — When in project mode (getHostProjectId() truthy):
    • The Workload list uses a 2-pronged “is my workload” check — pool ledger membership OR project/namespace label match — to avoid empty-list failures from FE/BE label mismatches (isMyWorkload, Workload/index.vue:540-545,927-929); project filter cannot be cleared via “Clear all” when locked (:697-698).
    • fetchWorkloads auto-injects the host project into project param server-side via injectProjectParam/getHostProjectId() fallback if caller doesn’t pass one explicitly (apis/index.ts:325-329).
  3. Priority class mapping — priorityClassToValue() (apis/index.ts:174-182): p1–p9 → 100–900; default-batch → 100; unknown → null. Priority badge display divides by 100 and prefixes P (Workload/index.vue:177). Missing priority defaults to display P1 in the Workload table (Workload/index.vue:173-178) even though the underlying value is null (scheduler treats unspecified as P1).
  4. GPU health badge / Inventory parity — the Workload table’s fault badge must use the exact same useFaultState composable as GpuInventory’s tree, “so users don’t have to decide which screen to trust” (Workload/index.vue:828-831 comment + code).
  5. Ownership + permission gate for Priority Run — WorkloadDetail only shows the “Priority Run” button if isMyWorkload && canEditWorkload (both conditions AND’d, referencing ticket “P2-F-B4”) — WorkloadDetail/index.vue:44.
  6. Status label translation — backend status Reserving is always displayed as “Waiting” to the user (statusLabel(), Workload/index.vue:993-996; also used in the State filter, :938-939).
  7. RBAC via route manifest (DERIVED_ONLY) — API-level permission is derived solely from the apis array attached to this route’s menu entry; any endpoint not listed there returns 403 for non-super-admin roles even if reachable in code (route-defs.ts:133-144 comment).

Data Flow

Workload list load cycle:
onMounted → loadMyPoolNames() [project mode only] + loadWorkloads() + loadFilters()
loadWorkloads():
if filters.cluster === 'all': fan-out fetchWorkloads() per allClusterTargets(), tag `_cluster`
else: fetchWorkloads() for the single selected cluster
loadFilters(): fetchProjects() + fetchGpuPoolList() (independent try/catch, non-blocking)
→ rows computed (derives type/category/priority/preemptible/gpuPool/resources/status/age)
→ filteredRows computed (applies project/pool/wlType/gpuType/state/text filters + isMyWorkload gate)
→ <Table :data="filteredRows"> renders, accordion expands to active_pods sub-rows
watch(filters.cluster) → loadWorkloads()
watch([schedulerClusters, clusters]) → re-fan-out if still on 'all' and clusters list changed
Workload row action — Stop/Delete/Detail:
User clicks WorkloadActionMenu (⋮) on a row
→ action.ts resolveStopAction(ownerKind) picks stop-isvc vs delete semantics
→ evictWorkload(namespace, podName, cluster) [POST .../actions/evict]
→ emits 'done' → Workload/index.vue.onWlDone() → loadWorkloads() (full refresh)
OR
→ emits 'detail' → openDetail(row) → router.push(SCHEDULER_WORKLOAD_DETAIL, \{cluster, namespace, name\})
Workload → Workload Detail → Priority Run:
WorkloadDetail mounted with route params \{cluster, namespace, name\}
→ fetches workload + topology data (nodes/pods) → renders VueFlow graph
→ if isMyWorkload && canEditWorkload: "Priority Run" button visible
→ openPriorityRun() → PriorityRunDialog opens
→ onPriorityPick(priority) → elevateWorkloadPriority(namespace, podName, priority, cluster)
[POST .../actions/direct-run]
→ dialog closes, page state refreshed to reflect new priority

Dependency Graph

src/pages/index.ts
├─(dyn import)→ src/modules/scheduler/dashboard/pages/Workload/index.vue
├─(dyn import)→ src/modules/scheduler/dashboard/pages/WorkloadDetail/index.vue
└─(dyn import)→ src/modules/scheduler/dashboard/pages/WorkloadLog/index.vue

src/router/index.ts ──uses names from── src/constants/routeNames/index.ts
src/router/route-defs.ts ──uses paths from── src/constants/routes/index.ts
routes.manifest.json ──generated/mirrors── src/router/route-defs.ts (per build-manifest script, referenced in route-defs.ts:1-9)

Workload/index.vue
├─→ @/modules/scheduler/dashboard/apis (fetchWorkloads, fetchProjects, fetchGpuPoolList, resolveCluster)
├─→ @/modules/scheduler/dashboard/apis/coreGpu (fetchCoreGpuPools)
├─→ @/modules/scheduler/dashboard/components/ClusterSelectGuard.vue
├─→ @/modules/scheduler/dashboard/components/WorkloadActionMenu/index.vue
├─→ @/modules/scheduler/fault/useFaultState
├─→ @/modules/scheduler/_host/useProfileStore
├─→ @/modules/scheduler/_host/utils/hostContext (getHostProjectId)
├─→ ./workloadHelpers.ts (workloadTypeOf/Label, gpuTypeOf/Label, gpuResourceText, formatDateTime, relativeAge)
├─→ @cloudz-mp/zmp-common-ui (Table, FormItem, Input, Select, Icon, Tag)
└─→ @/constants (ROUTE_NAMES)

WorkloadDetail/index.vue
├─→ @/modules/scheduler/dashboard/components/ClusterSelectGuard.vue
├─→ @/modules/scheduler/dashboard/components/PriorityRunDialog/index.vue
├─→ @/modules/scheduler/common/BackChip.vue
└─→ @vue-flow/core (VueFlow, Background, Controls, Panel, Handle, Position)

WorkloadLog/index.vue
├─→ @/modules/scheduler/dashboard/components/ClusterSelectGuard.vue
└─→ @/modules/scheduler/dashboard/apis (fetchPodLogs, presumed from apis module)

@/modules/scheduler/dashboard/apis/index.ts
├─→ @/modules/scheduler/_host/baseUi (services.axiosInstance, getRealm)
├─→ @/modules/scheduler/_host/utils/hostContext (getHostProjectId)
├─→ @/modules/scheduler/_host/utils/projectScope (injectProjectParam)
├─→ @/modules/scheduler/_host/useProfileStore
└─→ @/modules/scheduler/common/types/server, common/types/client (type defs)

Sequence Flow

A. Enter Workload list (project mode) and stop a workload:
  1. Router resolves projectGpuSchedulerWorkload at /proj/{project}/gpu/workload → loads Workload/index.vue.
  2. getHostProjectId() returns the project id → lockedProjectId/projectLocked = true; filters.project is pre-set and the Project select is disabled.
  3. onMounted → loadMyPoolNames() (pool ownership ledger) + loadWorkloads() + loadFilters() run concurrently.
  4. loadWorkloads() fans out fetchWorkloads('', '', cluster, lockedProjectId) across allClusterTargets() (since filters.cluster defaults to 'all').
  5. rows/filteredRows computed apply isMyWorkload() gating (pool-ledger OR project/namespace match) in addition to other filters.
  6. User opens WorkloadActionMenu on a row → chooses “Stop” → menu’s action.ts resolves stop semantics by ownerKind → calls evictWorkload(namespace, name, cluster) (POST /actions/evict).
  7. Menu emits done → onWlDone() → loadWorkloads() re-fetches and re-renders the table with the workload’s updated/removed state.
B. Workload → Workload Detail → Priority Run:
  1. User clicks a row (or its detail emit) → openDetail(row) → router.push(SCHEDULER_WORKLOAD_DETAIL, {cluster, namespace, name}).
  2. WorkloadDetail mounts with route params → fetches workload + topology data (nodes/pods) → renders VueFlow graph.
  3. If isMyWorkload && canEditWorkload, the “Priority Run” button is shown.
  4. openPriorityRun() opens PriorityRunDialog; user picks a priority.
  5. onPriorityPick(priority) → elevateWorkloadPriority(namespace, podName, priority, cluster) (POST .../actions/direct-run).
  6. Dialog closes; page state refreshes to reflect the new priority.

Key Findings

  • The Workload page is purely a read + narrow-write UI over the Volcano scheduler API surface centralized in src/modules/scheduler/dashboard/apis/index.ts.
  • It implements client-side multi-cluster fan-out (“All” cluster mode) because the backend /workloads endpoint is single-cluster only.
  • No dedicated Pinia store exists for Workload page state — only the shared useProfileStore (realm/cluster/project/namespace scope) is used; all page-specific data (workload list, filters, modal state) lives in local ref/reactive inside the SFC <script setup>.
  • The page routes project isolation through getHostProjectId() + a “GPU pool ownership ledger” (fetchCoreGpuPools) as an additional safety net beyond label matching — this pattern is explicitly called out in code comments as security-relevant (“설계 ④”, “IDOR fail-open 차단”).
  • The “Deploy Workload” modal does not call a create-workload API — it is a guided YAML-example generator that copies to clipboard or redirects to the host’s Model Manager (goModelDeploy()); there is no in-app workload deployment endpoint used here.

Risks / Technical Debt

  • Workload/index.vue is a large monolithic SFC (2000+ lines including styles) with substantial inline business logic (filter derivation, priority math) — no unit test files were found colocated for its main logic (only WorkloadActionMenu/tests exists under dashboard/).
  • Heavy reliance on loosely-typed any/Record<string, unknown> for raw workload shapes — normalization logic (normalizeWorkload) attempts to reconcile two backend response shapes, which is inherently fragile to backend contract drift.
  • Project-mode workload filtering trusts 2 independent signals (pool ledger + labels) with a fallback “allow through” behavior during the loading window before myPoolNames populates, which could transiently render unfiltered cross-project data.

Assumptions

  • WorkloadLog/index.vue’s log-fetching call to fetchPodLogs and its polling/streaming mechanism were Not Found in Code in the portion read (only the template’s left sidebar was inspected before the file was truncated); the “Streaming” tag suggests possible polling or SSE, but the mechanism was not confirmed.
  • WorkloadDetail/index.vue‘s exact topology-fetching composable/API call was Not Found in Code in the portion read (only template markup through line 150 was inspected); the calls to fetchWorkloadManifest/topology composables are inferred from sibling pages’ patterns (useClusterTopology) and the imports list, not directly confirmed line-by-line.

  • routes.manifest.json
  • src/router/route-defs.ts
  • src/router/index.ts
  • src/constants/routeNames/index.ts
  • src/constants/routes/index.ts
  • src/pages/index.ts
  • src/modules/scheduler/dashboard/pages/Workload/index.vue
  • src/modules/scheduler/dashboard/pages/Workload/workloadHelpers.ts
  • src/modules/scheduler/dashboard/pages/WorkloadDetail/index.vue
  • src/modules/scheduler/dashboard/pages/WorkloadLog/index.vue
  • src/modules/scheduler/dashboard/pages/WorkloadLog/logState.ts
  • src/modules/scheduler/dashboard/pages/GpuPoolDetail/index.vue (drill-down target)
  • src/modules/scheduler/dashboard/apis/index.ts
  • src/modules/scheduler/dashboard/apis/coreGpu.ts
  • src/modules/scheduler/dashboard/components/ClusterSelectGuard.vue
  • src/modules/scheduler/dashboard/components/WorkloadActionMenu/index.vue
  • src/modules/scheduler/dashboard/components/WorkloadActionMenu/action.ts
  • src/modules/scheduler/dashboard/components/PriorityRunDialog/index.vue
  • src/modules/scheduler/fault/useFaultState.ts
  • src/modules/scheduler/_host/useProfileStore.ts
  • src/modules/scheduler/_host/baseUi.ts
  • src/modules/scheduler/_host/utils/hostContext.ts
  • src/modules/scheduler/_host/utils/projectScope.ts
  • src/modules/scheduler/common/types/server/index.ts
  • src/modules/scheduler/common/types/client/index.ts
  • src/modules/scheduler/common/BackChip.vue
  • src/modules/scheduler/dashboard/stores/useQueueStore/index.ts
  • src/modules/scheduler/dashboard/stores/createQueueStore.ts
  • src/i18n/locales/en/dash-workload.ts (and ja/ko equivalents)