DanielLeens commented on PR #11733:
URL: https://github.com/apache/seatunnel/pull/11733#issuecomment-5237301511

   # What Problem Does This PR Solve?
   
   The Zeta Web UI's "Workers"/"Master" pages currently show only 4 raw fields 
from `/system-monitoring-information` (host, port, physical memory, heap used) 
and no per-worker slot/capacity view, even though the resource manager already 
tracks per-worker slot assignment 
(`WorkerProfile#assignedSlots`/`unassignedSlots`) for scheduling. This PR adds 
a new read-only `GET /resource/workers` endpoint that projects that existing 
state into a `WorkerOverviewInfo` DTO (host, port, totalSlot, usedSlot, 
dynamicSlot, cpuPercentage, memPercentage, attributes), joins it client-side 
with the existing monitoring table by `host:port`, and adds a details drawer 
("View") reusing the existing `Configuration` component. Docs (`web-ui.md`, 
`rest-api-v2.md`, EN/ZH) are updated accordingly.
   
   **Important context that changes the shape of this review:** the PR is 
currently **closed**, and the author (danielnadean) posted a withdrawal comment 
on the PR itself:
   
   > "Superseded - withdrawing in favor of @goutamadwant's proposal on #11665 
(correctly handles dynamic-slot semantics and reuses/aligns with the existing 
WorkerResourceDiagnostic and #11597 instead of adding a third slot model)."
   
   On the parent issue (#11665), the author further details this: their own 
`totalSlot = assignedSlots.length + unassignedSlots.length` computation is 
acknowledged to be wrong for dynamic-slot workers, and there is an existing 
`WorkerResourceDiagnostic` class 
(`org.apache.seatunnel.engine.server.diagnostic`) modeling almost the same 
per-worker shape (`totalSlots`, `freeSlots`, `dynamicSlot`, `cpuUsage`, 
`memUsage`) that this PR duplicates with different field names instead of 
reusing.
   
   Given that, I want to be upfront: I have never reviewed this PR before, so 
this is not a "the last round missed something" situation — but since the PR is 
already closed and the author has self-diagnosed the main design problem, this 
review's job is mainly to (a) independently confirm the author's own findings 
against the actual source so the reasoning is on record for whoever picks up 
the follow-up (@goutamadwant's proposal), and (b) surface a few additional, 
purely mechanical issues I found while verifying the diff, in case any of this 
code/tests gets reused going forward.
   
   ---
   
   # 1. Code Change Review
   
   ## 1.1 Core Logic Analysis
   
   **Runtime path traced from source (all confirmed by reading the current 
head, not just the PR description):**
   
   ```
   GET /resource/workers
     -> WorkerOverviewServlet#doGet
       -> WorkerOverviewService#getWorkerOverviewInfos
          - if this node is master (BaseService#getSeaTunnelServer(true)):
              GetWorkerOverviewOperation.getWorkerOverviewInfos(server)  
[local, same thread]
          - else:
              NodeEngineUtil.sendOperationToMasterNode(nodeEngine, new 
GetWorkerOverviewOperation()).join()
              [forwarded via Hazelcast operation service to the master node]
       -> GetWorkerOverviewOperation.getWorkerOverviewInfos(server)
          -> ResourceManager#getRegisterWorker()  
(AbstractResourceManager#registerWorker, a plain
             ConcurrentHashMap<Address, WorkerProfile> already populated by the 
existing worker
             registration/heartbeat path)
          -> GetWorkerOverviewOperation#toWorkerOverviewInfo(WorkerProfile) for 
each entry
             -> WorkerOverviewInfo{host, port, usedSlot=assignedSlots.length,
                totalSlot=assignedSlots.length+unassignedSlots.length, 
dynamicSlot,
                cpuPercentage/memPercentage from SystemLoadInfo, attributes}
   ```
   
   This is a correctly-wired, read-only projection: `getRegisterWorker()` is an 
existing `@Getter`-exposed `ConcurrentMap` (`AbstractResourceManager.java:60`), 
no new mutable state is introduced, and the local-vs-forward-to-master routing 
mirrors `OverviewService` exactly (`WorkerOverviewService.java` vs 
`OverviewService.java`). `WorkerOverviewService` even adds a defensive 
`nodeEngine.getMasterAddress() == null` check before forwarding that 
`OverviewService` lacks — a minor, harmless robustness improvement, not a 
behavior change for the existing endpoint.
   
   **The core correctness problem (confirmed independently, matches the 
author's own admission):**
   `GetWorkerOverviewOperation.java:306-315` computes:
   ```java
   int assignedSlots = workerProfile.getAssignedSlots() == null ? 0 : 
workerProfile.getAssignedSlots().length;
   int unassignedSlots = workerProfile.getUnassignedSlots() == null ? 0 : 
workerProfile.getUnassignedSlots().length;
   info.setUsedSlot(assignedSlots);
   info.setTotalSlot(assignedSlots + unassignedSlots);
   ```
   This mirrors `GetOverviewOperation.getOverviewInfo` 
(`GetOverviewOperation.java:91`, 
`overviewInfo.setTotalSlot(assignedSlots.size() + unassignedSlots.size())`) — 
so it is not a new bug pattern invented by this PR, it's the same "total = 
assigned + unassigned array lengths" convention already used for the 
cluster-wide `/overview` endpoint. However, for **dynamic-slot workers** 
(`WorkerProfile#dynamicSlot == true`), slots are created from remaining 
resource on demand rather than pre-allocated into a fixed `unassignedSlots` 
pool, so `unassignedSlots.length` does not represent "remaining capacity" the 
way it does for fixed-slot workers. Presenting `totalSlot = usedSlot + 
unassignedSlots.length` for a dynamic-slot worker is therefore misleading (as 
the author agrees) — it either always shows `total == used`, or shows a number 
that has no real capacity meaning, depending on what `unassignedSlots` happens 
to contain for the dynamic case. This is exactly the finding @goutamadwant rai
 sed on #11665 and the author accepted.
   
   **Model duplication:** `WorkerOverviewInfo` (new, this PR) and 
`WorkerResourceDiagnostic` (existing, 
`org.apache.seatunnel.engine.server.diagnostic.WorkerResourceDiagnostic`) both 
represent "per-worker slot/load snapshot" with different field names 
(`totalSlot`/`usedSlot` vs `totalSlots`/`freeSlots`, 
`cpuPercentage`/`memPercentage` vs `cpuUsage`/`memUsage`). 
`WorkerResourceDiagnostic` is currently only used from the pending-diagnostics 
collector path (`PendingDiagnosticsCollector`, `PendingClusterSnapshot`), not 
exposed over REST, but the two DTOs modeling the same concept independently is 
the kind of fragmentation that should be reconciled before a REST-facing v1 
ships, especially with #11597 also adding job-centric slot usage in parallel.
   
   **Broken doc link introduced by this diff:** 
`docs/en/engines/zeta/web-ui.md` and `docs/zh/engines/zeta/web-ui.md` both add 
a link to `worker-node-resource-view.md` ("See [Worker Node Resource 
View](worker-node-resource-view.md) for the design background."), but no such 
file exists anywhere in this diff or in the base tree (`find docs -iname 
"worker-node-resource-view*"` returns nothing). This correlates with the fork 
CI's `Dead links` job reporting `failure` on this exact head SHA (job id 
`93340292263`, run `31350524694`); I wasn't able to pull the raw job log 
(network error fetching the Azure blob log URL from this environment), but the 
missing target file is independently verifiable straight from the diff, and the 
referenced design doc lives in the separate, now-closed PR #11732 which this 
PR's description says should be merged first — it never was.
   
   ## 1.2 Compatibility Impact
   
   **Fully compatible.** New REST endpoint (`GET /resource/workers`), new 
servlet, new `GetWorkerOverviewOperation`/`GET_WORKER_OVERVIEW_TYPE = 13` (next 
unused id in `ResourceDataSerializerHook`, no collision with existing 0-12), 
new DTO, new frontend type/call. No existing config option, endpoint, 
serialization id, or default value is touched. The frontend gracefully degrades 
(`managerService.getWorkerOverview().catch(() => [])`) if the new endpoint is 
unreachable, so an old-worker/new-master (or vice versa) rolling upgrade 
wouldn't crash the UI, it would just show `-` in the new Slots column — 
acceptable given this is a purely additive, non-critical UI feature.
   
   ## 1.3 Performance / Side-Effect Analysis
   
   - The new call adds one extra `Promise` 
(`managerService.getWorkerOverview()`) per page load, run in parallel with the 
existing monitoring call (`Promise.all`), so no added latency on the happy path.
   - Server-side, `getRegisterWorker()` is an in-memory map read with a 
`.stream().map().collect()` over currently-registered workers — cheap, no new 
locks, no polling loop.
   - No new background thread, retry logic, or resource that could leak.
   
   ## 1.4 Error Handling and Logging
   
   ### Issue 1
   - **Location:** 
`seatunnel-engine/seatunnel-engine-ui/src/tests/managers.spec.ts:76-81` (and 
the corresponding component logic in 
`seatunnel-engine/seatunnel-engine-ui/src/views/managers/index.tsx:38,63-64,79-91`)
   - **Problem:** The test's new assertion 
`expect(wrapper.text()).toContain('2/4')` appears to be unreachable given the 
component's actual runtime behavior. `managers/index.tsx` calls `useRoute()` 
(line 38) and computes `isMaster = route?.path.endsWith('/master') || false`, 
then filters `monitorRes` to `row.isMaster === String(isMaster)` before mapping 
in the worker-overview join. `managers.spec.ts` mounts the component with only 
`plugins: [i18n]` — no router plugin/mock is installed, unlike the sibling test 
`detail.spec.ts` (`src/tests/detail.spec.ts:32-34`), which explicitly does 
`vi.mock('vue-router', () => ({ useRoute: () => ({...}), ... }))` for exactly 
this reason. Without that mock, `useRoute()` returns `undefined` under 
vue-test-utils' `mount`, so `route?.path...` short-circuits to `undefined`, and 
`isMaster` defaults to `false`. That means the filter keeps only `mockData[1]` 
(`isMaster: 'false'`, port `5802`), which has **no** matching entry in 
`mockWorkerOverview` (which
  only defines port `5801`) — so the rendered table would show a single row 
with the Slots column as the fallback `'-'`, and the row that would render 
`'2/4'` (port `5801`, `isMaster: 'true'`) is filtered out entirely before the 
join even matters.
   - **Potential risk:** As written, this assertion looks like it would fail if 
actually executed — but it never gets the chance to: the CI job that runs `npm 
run test:unit` (`Build SeaTunnel UI` / `seatunnel-ui` job in 
`.github/workflows/backend.yml:431-433`) is gated by `if: 
needs.changes.outputs.api == 'true'` (`backend.yml:432`), and the `api` 
path-filter only covers `seatunnel-api/**`, `seatunnel-common/**`, 
`seatunnel-core/**`, etc. (`backend.yml:143`) — it does **not** include 
`seatunnel-engine/**` or `seatunnel-engine-ui/**`. This PR only touches those 
two paths, so the job is `skipped` on this exact head SHA (confirmed via the 
fork's check-runs: `Run / Build SeaTunnel UI | completed | skipped`). In other 
words, the new/modified frontend unit test is never executed by this PR's own 
CI run, so its correctness is currently unverified by any automated gate.
   - **Best improvement:** Add the same `vi.mock('vue-router', ...)` pattern 
used in `detail.spec.ts` (mocking `path` to end in `/workers` or `/master` as 
appropriate for the scenario under test), so the filter behavior is 
deterministic and the assertions actually exercise the intended row.
   - **Severity:** High (the test as written does not appear to validate what 
it claims to, and no CI job currently catches that for engine/UI-only changes).
   - **Raised by another reviewer:** No — the author's own withdrawal comment 
on this PR does not mention this specific test issue; it's a separate, 
independent finding on top of the design-level dynamic-slot issue the author 
already flagged.
   
   ### Issue 2
   - **Location:** `docs/en/engines/zeta/web-ui.md:68`, 
`docs/zh/engines/zeta/web-ui.md:69`, `docs/en/engines/zeta/web-ui.md:87`, 
`docs/zh/engines/zeta/web-ui.md:88`
   - **Problem:** Both docs link to `worker-node-resource-view.md`, a file that 
does not exist anywhere in this diff or the base tree.
   - **Potential risk:** Broken link ships in user-facing documentation; 
correlates with the fork's `Dead links` check reporting `failure` on this head 
SHA.
   - **Best improvement:** Either add the missing design doc (and keep it in 
sync with wherever the design ultimately lands, since PR #11732 that was 
supposed to add it is also closed), or drop the link until a design doc 
actually exists.
   - **Severity:** Medium (docs-only breakage; note the `Dead links` CI job has 
`continue-on-error: true` per `backend.yml:112`, so this would not have blocked 
merge on its own, but it's still a real broken link worth fixing).
   - **Raised by another reviewer:** No.
   
   ---
   
   # 2. Code Quality Assessment
   
   ## 2.1 Coding Standards
   
   New Java classes (`GetWorkerOverviewOperation`, `WorkerOverviewInfo`, 
`WorkerOverviewService`, `WorkerOverviewServlet`) all carry class-level Javadoc 
explaining scope and constraints ("read-only projection... introduces no new 
persisted or mutable state"), which is good practice given this codebase's 
guidance to document lifecycle/runtime-path classes. The one non-trivial 
private/package-private method, `toWorkerOverviewInfo`, has an inline comment 
explaining why it's package-private (`// package-private for direct unit 
testing without mocking the SeaTunnelServer chain`) — appropriate. 
`WorkerOverviewService` and `WorkerOverviewServlet` lack per-field/per-method 
Javadoc beyond the class-level comment, but their methods are short, 
single-purpose, and named the same way as their 
`OverviewService`/`OverviewServlet` counterparts, which is acceptable given the 
existing convention in this package. `WorkerOverviewInfo`'s fields are simple 
typed DTO fields without individual field comme
 nts; given the class-level Javadoc already explains the DTO's purpose and each 
field name is self-describing (`host`, `port`, `totalSlot`, ...), I would not 
block on this, but a one-line comment on `cpuPercentage`/`memPercentage` 
clarifying they're `Double` (nullable) specifically because a 
freshly-registered worker may not have reported `SystemLoadInfo` yet would 
remove any ambiguity for future readers.
   
   ## 2.2 Test Coverage and Test Stability
   
   **Backend:** `GetWorkerOverviewOperationTest` (new) covers the pure mapping 
function `toWorkerOverviewInfo` with two cases — a fully-populated 
`WorkerProfile` and one with null slot arrays/load info (verifying 
zero-instead-of-throw). Both cases use the real 
`WorkerProfile`/`SlotProfile`/`ResourceProfile` constructors correctly 
(verified against current signatures) and should compile and pass. However, 
neither test case exercises a **dynamic-slot** worker, which is precisely the 
scenario the author's own withdrawal identifies as broken — a gap directly 
relevant to the PR's actual defect.
   
   **Frontend flaky-test-risk rating: Risk present.** See Issue 1 above — 
`managers.spec.ts`'s new `toContain('2/4')` assertion appears logically 
unreachable given the component's routing-dependent filter and the absence of a 
`vue-router` mock, and the CI job that would run it (`Build SeaTunnel UI`) is 
skipped for this PR's file set (`api` path-filter doesn't cover 
`seatunnel-engine/**`/`seatunnel-engine-ui/**`). This isn't "flaky" in the 
intermittent-failure sense — it looks deterministically wrong — but it is 
unverified, which for this review's purposes I'm treating the same way: don't 
trust it as passing evidence without fixing the router mock.
   
   ## 2.3 Documentation Updates
   
   `docs/en/engines/zeta/rest-api-v2.md` and `docs/zh/.../rest-api-v2.md` both 
get a new endpoint section with matching parameter/response shape; `web-ui.md` 
(EN/ZH) get matching updates to the Workers/Master feature descriptions and 
table. Content stays in sync between EN and ZH. The one gap is the dangling 
`worker-node-resource-view.md` link covered in Issue 2.
   
   ---
   
   # 3. Architectural Soundness
   
   ## 3.1 Elegance of the Solution
   
   The read-only-projection approach and REST wiring pattern (mirroring 
`OverviewService`/`OverviewServlet`) is the right shape for this kind of 
feature and is not a "workaround" — it's a clean, minimal addition to an 
existing, well-understood pattern. The specific `totalSlot` computation is 
where it falls short, per the author's own analysis: it's a precise fix for 
fixed-slot workers but an incorrect one for dynamic-slot workers, and it 
introduces a DTO that duplicates `WorkerResourceDiagnostic`'s shape rather than 
building on it.
   
   ## 3.2 Maintainability
   
   Straightforward to maintain in isolation, but two competing per-worker slot 
DTOs (`WorkerOverviewInfo` here, `WorkerResourceDiagnostic` existing) with 
different field names for the same underlying concepts is a maintenance hazard 
going forward — future changes to slot semantics would need to be kept in sync 
across both, or one becomes stale. This is exactly the reason the author gave 
for withdrawing.
   
   ## 3.3 Extensibility
   
   The client-side `host:port` join pattern (`overviewByAddress` map in 
`managers/index.tsx:774-777`) is a reasonable, extensible way to combine two 
independently-sourced per-node datasets without server-side coupling, and the 
`WorkerRow` type's `Partial<...>` fields correctly model that the two sources 
can be out of sync (a monitored node without a matching resource-manager 
registration, or vice versa).
   
   ## 3.4 Historical-Version Compatibility
   
   No historical/serialization compatibility concerns: this is a purely 
additive REST endpoint and DTO, no existing wire format, config option, or 
persisted state is touched or reinterpreted.
   
   ---
   
   # 4. Issue Summary
   
   | Number | Issue | Location | Severity |
   |---|---|---|---|
   | 1 | New `managers.spec.ts` assertion (`toContain('2/4')`) appears 
unreachable given routing-dependent row filtering with no `vue-router` mock, 
and the CI job that would run it is skipped for engine/UI-only diffs | 
`seatunnel-engine/seatunnel-engine-ui/src/tests/managers.spec.ts:76-81` | High |
   | 2 | Docs link to a design doc (`worker-node-resource-view.md`) that does 
not exist in this diff or the base tree; correlates with the fork's `Dead 
links` CI failure | `docs/en/engines/zeta/web-ui.md:68,87`, 
`docs/zh/engines/zeta/web-ui.md:69,88` | Medium |
   | 3 (design, self-identified by author) | `totalSlot = assignedSlots.length 
+ unassignedSlots.length` is misleading for dynamic-slot workers, since 
`unassignedSlots` isn't a fixed remaining-capacity pool in that mode | 
`GetWorkerOverviewOperation.java:306-315` | High |
   | 4 (design, self-identified by author) | New `WorkerOverviewInfo` DTO 
duplicates the existing `WorkerResourceDiagnostic` concept with different field 
names instead of reusing/aligning with it | 
`resourcemanager/resource/WorkerOverviewInfo.java` vs 
`diagnostic/WorkerResourceDiagnostic.java` | Medium |
   
   ---
   
   # 5. Merge Recommendation
   
   ### Conclusion: Not recommended for merge
   
   1. **Blockers — must be fixed:**
      - The PR is already **closed**, and the author has explicitly withdrawn 
it in favor of @goutamadwant's proposal on #11665, which is scoped to correctly 
handle dynamic-slot semantics and reconcile with `WorkerResourceDiagnostic` and 
#11597 instead of introducing a third, competing slot model (Issue 3, Issue 4 
above — both self-identified by the author, and independently confirmed against 
source in section 1.1).
      - If any part of this implementation is reused in the follow-up work, the 
frontend test gap (Issue 1) and the broken doc link (Issue 2) should be fixed 
before that follow-up ships, since neither is currently caught by CI for this 
file set.
   
   2. **Recommended fixes — non-blocking (for whoever picks up the follow-up 
issue):**
      - Reuse/align with `WorkerResourceDiagnostic`'s field naming and 
dynamic-slot representation rather than introducing a parallel DTO.
      - Add a `vi.mock('vue-router', ...)` to any future 
`managers.spec.ts`-style test, following the existing `detail.spec.ts` pattern, 
so route-dependent filtering is actually exercised.
      - Keep the design-doc link only once the referenced doc actually exists, 
or land both PRs together.
   
   **Overall assessment:** The mechanical wiring in this PR (servlet 
registration, serializer hook id, master-forwarding routing, DTO plumbing) is 
solid and closely follows existing, proven patterns in this codebase — that 
part is a good template to build on. But the core per-worker slot semantics are 
wrong for dynamic-slot workers, and the DTO duplicates existing modeling, both 
of which the author has already acknowledged and is the direct cause of 
withdrawing this PR. Combined with an unverified frontend test and a docs link 
to a file that doesn't exist, this PR in its current form shouldn't be merged 
as-is — which matches where it already stands (closed). This review is intended 
to leave a clear, source-verified record for whoever implements the follow-up 
under @goutamadwant's proposal.
   
   
   ---
   *Note: this PR was closed by the author during this review round, so this is 
posted as a regular comment rather than a formal review.*
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to