Workload
Part of the Scheduler feature area (axap-gpu-volcano-ui, MFE id 'scheduler'). Sibling docs: gpu-overview, gpu-inventory, timetable.
Overview
Workload (schedulerWorkload) is a filterable table of Volcano PodGroup workloads
across clusters/projects/pools, with a “Deploy Workload” guide modal. It lives at
modules/volcano/dashboard/pages/Workload/index.vue
with a helper module
modules/volcano/dashboard/pages/Workload/workloadHelpers.ts.
The backend is Volcano via the FastAPI service axap-gpu-volcano (prefix /scheduler/v1), plus zmp-core-api (/core/v1) for the Project filter’s option list.
A demo mode is baked into this codebase and is ON by default
(modules/volcano/dashboard/demo/isDemo.ts)
— disabled only per-tab via ?demo=0. In demo mode this page uses demoWorkloads() from
src/modules/volcano/dashboard/demo/demoMetrics.ts for both the workload list and the
derived filter options (pools/projects), instead of calling the real backend.
Route Design
Route constants: constants/routeNames/index.ts,
paths: constants/routes/index.ts, lazy components:
pages/index.ts. Registered in both the system and project route
tables built by buildRoutes() in router/index.ts.
| Route name | Raw path | System path | Project path | Component |
|---|---|---|---|---|
| ROUTE_NAMES.WORKLOAD (schedulerWorkload) | /workloads | /system/scheduler/workloads | /proj/:projectId/scheduler/workloads | Workload/index.vue — router/index.ts |
No hidden/drill-down routes belong to this screen itself; it does, however, link out to two things that are not routes inside this repo: (a) a Model Manager “Create Model” page (external app, via goModelDeploy), and (b) nothing else — the “커스텀 배포하기” branch stays in-modal (renders a YAML example, does not navigate).
Route mechanics common to the whole Scheduler MFE (system vs. project route table selection, getHostProjectId() scope-locking) are the same as documented in gpu-overview.
UI Structure
Workload → Workload/index.vue
├── Page head: title + description + "+ 워크로드 배포" button
├── Filter bar: 클러스터 / 프로젝트(전체+list) / GPU 풀(전체+list) /
│ 워크로드 타입(Inference/RayJob) / GPU 타입(FULL/FULL_NVLINK/MIG/TS) + 검색 + active-filter tags
├── Loading / Error states
├── Workload table (one row per PodGroup):
│ 클러스터 | 프로젝트 | GPU 풀 | 워크로드(name+status badge) | 워크로드 타입 | GPU 타입 | GPU 자원 | 배포내역(created+age+ID)
└── "워크로드 배포" 2-step modal (Teleport to body)
├── Step 1: 모델 매니저로 배포하기 (external link-out) | 커스텀 배포하기 (→ Step 2)
└── Step 2: 워크로드 종류 tabs + GPU 타입 select + type-specific resource inputs
+ read-only YAML 예시 (copy-to-clipboard)
Component Design
pages/Workload/index.vue is a single large page component (no extracted child components beyond the inline modal markup):
- Filter bar state: filters (reactive: cluster, project, pool, wlType,
gpuType, text); options are derived reactively:
- clusterOptions — union of resolvedCluster (from resolveCluster()) and profileStore.clusters.
- projectOptions/poolOptions — loaded async by loadFilters() (see API Design), or derived from demoWorkloads() in demo mode.
- hasActiveFilters — true if any of project/pool/wlType/gpuType is set (drives the “active filter tags”/“모두 지우기” row).
- Table rows are derived from workloads.value filtered by filters, mapped through
modules/volcano/dashboard/pages/Workload/workloadHelpers.tshelpers: workloadTypeOf (Inference/RayJob classification), gpuTypeOf (FULL/FULL_NVLINK/MIG/TS classification), gpuResourceText (human-readable resource string), workloadIdOf (deploy-history ID), formatDateTime/relativeAge (배포내역 column). - Deploy modal (createOpen, step ref 1|2, form reactive: wlKind, gpuType,
gpuCount, migProfile, migCount, vgpuCores, vgpuMemGb):
- Step 1 choice cards: “모델 매니저로 배포하기” (goModelDeploy()) vs. “커스텀 배포하기” (step = 2).
- Step 2 renders a computed exampleManifest (YAML text) that changes based on form.wlKind/form.gpuType/resource inputs, with a copyExample() clipboard action (copied flag flips the button label to “복사됨 ✓”).
- No backend call is made from this modal — it is guide-only (see Business Logic).
API Design
All calls use services.axiosInstance from @cloudz-mp/zmp-base-ui.
Base path resolution
Shared with the other screens — see
gpu-overview for resolveRealm()/
resolveCluster()/getRealmClusterPath()/getRealmPath() precedence rules
(modules/volcano/dashboard/apis/index.ts).
Endpoints used by Workload
| Function | Method & path | Notes |
|---|---|---|
| fetchWorkloads('', '', resolvedCluster) | GET {realmCluster}/workloads (cluster explicit, no namespace/pool filter) | called from loadWorkloads(); demo mode substitutes demoWorkloads() |
| fetchProjects() | GET {corePath}/projects?page=0&size=65535 | called from loadFilters() to populate the 프로젝트 select; failure is caught independently so the workload list still renders |
| fetchGpuPoolList() | derives from fetchVolcanoQueues() (maps q.metadata.name → {label, value}) | called from loadFilters() to populate the GPU 풀 select; failure caught independently |
Both loadWorkloads() and loadFilters() are invoked in parallel from onMounted() (no await between them), so the table can render before filter options finish loading.
No mutation endpoints (create/update/delete workload) are called from this screen — see Business Logic rule “Deploy Workload modal is guide-only”.
Dev-time proxy: /scheduler/v1 → axap-gpu-volcano (localhost:8000); /core/v1 →
zmp-core-api (localhost:8081) — vite.config.ts.
State Management
- No Pinia store is used by this page — workloads, filters, loading, error, projectOptions, poolOptions, createOpen/step/form/copied are all page-local ref/reactive state in Workload/index.vue.
- useProfileStore (
stores/useProfileStore.ts) is read for profileStore.clusters (merged into clusterOptions) — no direct scope-filter writes happen from this page (there is no ScopeFilter component on this screen; filters are page-local<select>s, not the shared cross-page ScopeFilter).
Business Logic
- Workload status derivation — same rule as GPU Overview (see gpu-overview item 1): status badge (statusTone/statusLabel) is driven by the normalized VolcanoWorkload.status from normalizeWorkload().
- Deploy Workload modal is guide-only — no create-workload POST call exists anywhere in Workload/index.vue; the modal only (a) redirects to Model Manager (goModelDeploy), or (b) renders a copyable YAML annotation example based on the selected workload/GPU type. The actual deployment happens outside this app.
- GPU-type-specific example manifest fields: FULL/FULL_NVLINK → GPU count field (nvidia.com/gpu); MIG → MIG profile (MIG_PROFILES list) + count; else (Time-Slicing) → vGPU cores (%) + vGPU memory (GB) — drives which input row is shown in Step 2 and what the generated exampleManifest contains.
- Independent filter-option failure isolation — fetchProjects() and fetchGpuPoolList() are each wrapped in their own try/catch inside loadFilters(), so a failure of one (e.g. core-api down) does not block the other, nor does it block loadWorkloads()’s table render.
- Cluster/GPU-pool/project filtering is entirely client-side — fetchWorkloads is called once per the resolved cluster (no server-side project/pool query params passed); all filters.* selects narrow the already-fetched workloads.value in memory via filteredRows.
- Demo mode is default-ON — same cross-cutting rule as the other three screens; isDemoMode() swaps both the workload list (demoWorkloads()) and filter options (derived from that same demo dataset, to stay in sync) in loadWorkloads()/ loadFilters().
Data Flow
flowchart TD
R[/system/scheduler/workloads/] --> P[Workload/index.vue]
P -->|onMounted| L1[loadWorkloads]
P -->|onMounted| L2[loadFilters]
L1 --> DEMO1{isDemoMode?}
DEMO1 -->|yes| D1[demoWorkloads]
DEMO1 -->|no| A1[apis: fetchWorkloads]
A1 --> BE[(axap-gpu-volcano /scheduler/v1)]
L2 --> DEMO2{isDemoMode?}
DEMO2 -->|yes| D2[demoWorkloads -> derive pool/project options]
DEMO2 -->|no| A2[apis: fetchProjects]
DEMO2 -->|no| A3[apis: fetchGpuPoolList -> fetchVolcanoQueues]
A2 --> BE2[(zmp-core-api /core/v1)]
A3 --> BE
P --> T[filteredRows table render]
P -->|+ 워크로드 배포| M[Deploy modal - guide only, no API call]
Dependency Graph
flowchart LR
Workload --> workloadHelpers["workloadHelpers.ts"]
Workload --> useProfileStore
Workload --> apisIndex["apis/index.ts (scheduler/v1, core/v1)"]
apisIndex --> useProfileStore
Sequence Flow
Workload list load
sequenceDiagram
actor User
participant Page as Workload/index.vue
participant API as apis/index.ts
participant BE as axap-gpu-volcano
participant CoreBE as zmp-core-api
User->>Page: navigate to /system/scheduler/workloads
Page->>Page: onMounted -> loadWorkloads() + loadFilters() (parallel)
alt demo mode
Page->>Page: workloads = demoWorkloads()
else real backend
Page->>API: fetchWorkloads('', '', resolvedCluster)
API->>BE: GET /scheduler/v1/realms/{r}/clusters/{c}/workloads
BE-->>API: raw items
API->>API: normalizeWorkload() per item
API-->>Page: VolcanoWorkload[]
end
par filter options
Page->>API: fetchProjects()
API->>CoreBE: GET /core/v1/realms/{r}/projects
CoreBE-->>API: projects
API-->>Page: projectOptions
and
Page->>API: fetchGpuPoolList()
API->>API: fetchVolcanoQueues()
API->>BE: GET .../volcano-queues
BE-->>API: queues
API-->>Page: poolOptions
end
Page-->>User: renders filter bar + table (filteredRows)
Deploy Workload modal (guide-only, no API call)
sequenceDiagram
actor User
participant Page as Workload/index.vue
User->>Page: click "+ 워크로드 배포"
Page->>Page: createOpen = true, step = 1
alt choose 모델 매니저로 배포하기
User->>Page: click "모델 매니저로 배포하기"
Page->>Page: goModelDeploy() (external link-out)
else choose 커스텀 배포하기
User->>Page: click "커스텀 배포하기"
Page->>Page: step = 2
User->>Page: select 워크로드 종류 / GPU 타입 / resource inputs
Page->>Page: exampleManifest recomputed (client-side only)
User->>Page: click "예시 복사"
Page->>Page: copyExample() -> clipboard write, copied = true
end
Page-->>User: modal remains a static guide; no network request fired
Key Findings
- This screen has no dedicated “create workload” backend endpoint call — despite having a prominent “+ 워크로드 배포” button, the entire flow is either an external link-out or a static documentation/example generator.
- No shared ScopeFilter component — unlike GPU Overview and Time Table, this page
implements its own inline filter
<select>s rather than reusing components/ScopeFilter/index.vue, so filter UX (e.g. “All” semantics, locked project in project mode) is not guaranteed to be identical to the other screens. - All cluster/project/pool/type filtering happens client-side after a single unfiltered fetchWorkloads call for the resolved cluster — this page does not use the namespace/gpuPool query params that fetchWorkloads() supports.
Risks / Technical Debt
- No project-mode scope lock observed — this page does not call getHostProjectId() (unlike GPU Overview/Time Table), so it is unclear whether project-mode users see only their project’s workloads or all workloads with just a project filter defaulting to their id. Treat project-mode behavior on this screen as Not Found in Code / unconfirmed.
- No error normalizer — error.value = e instanceof Error ? e.message : '알 수 없는 오류' in loadWorkloads(); loadFilters()’s two try/catch blocks only console.error and leave options empty on failure, with no user-facing error surfaced for that partial failure.
- Single-cluster only — fetchWorkloads('', '', resolvedCluster) always targets one resolved cluster; the “클러스터” filter’s non-active-cluster options (from profileStore.clusters) narrow an already cluster-scoped dataset rather than triggering a re-fetch against another cluster (re-fetch-on-cluster-change behavior was not confirmed in this pass).
Assumptions
- The exact filteredRows computed filtering logic (how filters.text free-text search matches across name/namespace/project/pool) was referenced by the filter placeholder text (“워크로드 · 네임스페이스 · 프로젝트 · 풀 검색”) but its implementation was Not Found in Code in this pass (not read).
- Whether changing the “클러스터” <select> triggers a re-fetch of fetchWorkloads for the newly selected cluster, or only filters the existing single-cluster dataset, is Not Found in Code / unconfirmed in this pass.
- WORKLOAD_KINDS and MIG_PROFILES constants (used in the deploy modal) were referenced but their definitions were not opened — treat their exact values as Not Found in Code here.