Fix CellPipe topology FQCN naming#4835
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4835 +/- ##
==========================================
+ Coverage 60.28% 60.37% +0.08%
==========================================
Files 971 971
Lines 92463 92529 +66
==========================================
+ Hits 55745 55866 +121
+ Misses 36718 36663 -55
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Greptile SummaryThis PR fixes the CellPipe topology FQCN regression introduced in #4801 (NVBug 6371056): pipe cells are now named with a single
Confidence Score: 5/5The routing fix is well-targeted and covered by new and updated unit tests; the identity and auth changes correctly handle explicit alias markers at all depths. The core FQCN naming change is confined to nvflare/fuel/utils/pipe/cell_pipe.py — relay branch of Important Files Changed
Reviews (27): Last reviewed commit: "Merge branch 'main' into nvbugs-6371056-..." | Re-trigger Greptile |
chesterxgchen
left a comment
There was a problem hiding this comment.
What changed:
- CellPipe FQCN naming changed from hierarchical runtime segments like:
site-1.job-123.active
to topology-aligned leaf names like:
site-1.job-123_active
See /private/tmp/nvflare-pr4835/nvflare/fuel/utils/pipe/cell_pipe.py:50. This makes the FQCN parent site-1, which is the cell the
subprocess actually connects to, instead of the unconnected pseudo-parent site-1.job-123.
-
Routing tests now cover the reported case: a pipe cell named site-1.job-123_active, connected only to site-1, can route to
server.job-123 through site-1. See /private/tmp/nvflare-pr4835/tests/unit_test/fuel/f3/cellnet/core_cell_routing_test.py:46. -
Identity/auth code was adjusted so these alias-style CellPipe stream cells still authenticate as the owning site, but only on the
stream channel and under the same parent. See /private/tmp/nvflare-pr4835/nvflare/private/fed/authenticator.py:306. -
It does not generally change CoreCell._try_find_ep() routing behavior, except comments. The fix is mainly “name the CellPipe cell so
the existing parent fallback works.”
Validation I ran on PR 4835:
90 passed
for:
tests/unit_test/fuel/utils/pipe/cell_pipe_test.py
tests/unit_test/fuel/f3/cellnet/core_cell_routing_test.py
tests/unit_test/fuel/f3/cellnet/identity_binding_test.py
tests/unit_test/private/fed/authenticator_test.py
Does it fix the reported problem?
Likely yes for the primary failure:
ext-process + streaming -> target_unreachable on server.
The new naming restores the parent lookup path described in your root cause. For site-1.job-123_active, the parent fallback is site-1,
and that is the connected client cell, so routing to server. should no longer die at TARGET_UNREACHABLE.
What it does not fix:
- The secondary logger bug is still there:
self.logger.log(f"exception from do_learn_task: ...")
in /private/tmp/nvflare-pr4835/nvflare/app_common/ccwf/client_ctl.py:464. That still should be self.logger.error(...).
- The “job ends FINISHED:COMPLETED after TASK_ABORTED” behavior is not addressed by this PR. If the CellPipe routing fix prevents the
abort, that symptom disappears for this case, but the controller semantics are unchanged for any future abort path.
My read: PR 4835 fixes the actual stream-routing regression, but it is incomplete relative to the full issue report because it leaves
the logger typo and abort/status behavior untouched. It also has unit coverage, not a full POC ext-process streaming regression test.
There was a problem hiding this comment.
@nvidianz before you merge, can you address my comments
|
@chesterxgchen Thanks for the detailed review — to your points (updated: all three are now addressed in this PR):
Please take another look when you get a chance. |
An exception from do_learn_task was only logged; the client never set the error in its status report, so the ccwf server controller never saw the failure and the job ended FINISHED:COMPLETED. Record the error so the next status report triggers system_panic on the server, ending the job as FINISHED:EXECUTION_EXCEPTION. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Follow-up on the two remaining review points — both are now part of this PR: 1. FINISHED:COMPLETED after TASK_ABORTED — addressed at the ccwf level in 8837e2c. 2. Ext-process streaming regression coverage — 44fa08d adds an
Ran on this branch (1 server, 2 clients): Remaining follow-up (separate PR): push error status reports to the server immediately via aux message instead of piggybacking on task pulls, closing the end-of-run race window where a recorded error might not be delivered. |
Groups the two existing ext-process Client API test cases (np_loop_cell_pipe and pt_large_model_pass_through) so the subprocess CellPipe -> server.<jobid> streaming routing path broken by the FQCN naming regression can be exercised on its own as a regression test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Review: PR #4835 — Fix CellPipe topology FQCN naming The PR fixes NVBug 6371056 by collapsing the CellPipe token+mode into a single leaf segment (site-1._active instead of site-1..active), so the FQCN parent matches the physically connected cell and cross-family routing to server. no longer falls back to an unconnected parent. It rewrites the stream-alias check in authenticator.py to a parent-equality + owner-leaf rule, and updates routing/identity/auth test coverage accordingly. The core fix is sound — the two high-severity candidates my finders raised (peer-FQCN disagreement between pipe ends, relay+VIA_ROOT auth rejection) were both refuted on verification: pipe ends provably compute names from identical exported conn props, and the VIA_ROOT scenario is pre-existing on main and unreachable via ScriptRunner. Surviving findings (most severe first)
|
Address review findings on the alias naming: - The <owner>_<runtime_id>_(active|passive) grammar was independently encoded in cell_pipe.py (build), identity.py (parse) and authenticator.py (re-parse). Move it to fqcn.py as make_cell_pipe_alias/parse_cell_pipe_alias so the three sites cannot drift apart. - Fail fast on tokens that break the naming: "." always splits the name into extra FQCN segments; "_" breaks alias parsing when the cell connects through another cell. "_" stays allowed in non-alias forms since the simulator uses the "simulate_job" token. - Correct the _cell_fqcn comment: root-connected pipe cells do NOT have a connected FQCN parent; cross-reference the load-bearing routing fall-through in CoreCell._try_find_ep and its test. - Warn when conn props carry no parent FQCN instead of silently fabricating a topology-shaped name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@chesterxgchen Thanks — findings 1-4 are addressed in 67ef2db; 5 and 6 declined with rationale below. 1. Token validation — 2. Grammar triplication — the alias grammar now lives in one place: 3. Wrong comment — rewritten: the comment now states explicitly that root-connected pipe cells (simulator/root-url) have an unconnected FQCN parent and rely on the 4. Silent fallback — the missing-parent-FQCN branch now logs a warning so a misconfiguration that produces a correct-looking name is diagnosable. 5. Identical branches — declined as you anticipated: with the warning from (4), the 6. Test file naming — keeping Aside ( All affected unit suites pass (107 targeted + 164 across cellnet/pipe), style checks clean. |
|
Review: PR #4835 — Fix CellPipe topology FQCN naming Overview. The PR fixes the NVBug 6371056 routing regression from #4801 by collapsing the CellPipe token and mode into one leaf segment (site-1._active instead of site-1..active) so a subprocess cell's FQCN parent matches the cell it actually connects to, reintroduces a shared alias grammar (make_cell_pipe_alias/parse_cell_pipe_alias) for relay-connected pipes, and updates mTLS identity resolution and stream auth accordingly. It also bundles an unrelated ccwf fix that reports do_learn_task exceptions to the server. The core routing fix is sound — I verified (and rejected) candidates claiming peer-name divergence between pipe ends and broken relay stream auth; both ends of a pipe pair always derive the same parent from shared conn props, and the relay auth paths improve under this PR. Findings (most severe first):
Verified and dismissed (for the record): relay vs CP-behind-relay pipe ends computing different names (both ends always share conn props, so parents always match); the _ token ValueError behind relays (unreachable from every documented/in-tree path — standalone agents always take the ROOT_SERVER branch); VIA_ROOT stream auth for relay-registered clients (rejected identically on base — pre-existing, and this PR improves the supported VIA_CP/VIA_RELAY paths); and the ccwf abort-path error report (in-tree do_learn_task implementations return cleanly on abort, and workflow_done gates any post-abort report from reaching the server). The one item I'd treat as blocking is finding 1 — it's a hard regression of the documented secure 3rd-party integration path, with an executed repro. Findings 2–4 are act-on-but-minor. |
Address review findings on the CellPipe topology naming:
1. CellIdentityResolver only treats a leaf as a CellPipe alias when the
FQCN is a single segment (legacy sibling alias) or a direct child of
the local cell (a pipe connected through this cell, e.g. a relay).
Anywhere else the leaf is a plain <token>_<mode> segment whose token
may contain "_" (e.g. site-1.ext_trainer_active), and alias-parsing
it fabricated a wrong owner ("ext"), breaking mTLS identity
resolution for the documented secure external-trainer integration.
2. _cell_fqcn only rejects "." in tokens for CP/relay-connected pipes,
where extra FQCN segments would recreate an unconnected parent.
Root-connected cells route via the root fall-through regardless of
depth, so dotted agent ids keep working as they did before.
3. ClientSideController._do_learn uses the fl_ctx-aware log_exception
helper like its sibling exception paths.
4. Rename test_client_ctl.py to client_ctl_test.py to follow the
documented [module_name]_test.py convention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
PR #4835 — CellPipe topology FQCN naming: three-angle review Architect: sound, with concerns The core move is architecturally correct, not a workaround: in CellNet, the FQCN is the routing address, and PR #4801 broke the invariant "address hierarchy ≈ connection topology." Naming the subprocess cell site-1._active makes its FQCN parent the cell it's physically connected to, so routing works through unmodified generic logic — the PR touches core_cell.py only in comments. A plain revert of #4801 would not have fixed the bug (the pre-#4801 flat name is gen-1 and fails the same routing dead-end at core_cell.py:1209), so the "third naming scheme" is justified: it's the first scheme that satisfies routing and mTLS identity simultaneously. Concerns: the divergence class isn't eliminated — root-connected pipes still rely on a self-described "load-bearing" fall-through, so the invariant is conditional and a future cell type can silently trip the same gen>1 dead-end. The underscore now does double duty (separator inside a segment), contained by a right-anchored grammar and construction-time rejection seam is untested (below).Mixed-version compatibility is actually good — legacy flat aliases are still accepted Principal engineer: core fix is correct (verified, not assumed) The reviewer traced the failing and fixed paths end-to-end, confirmed the Non-blocking findings: (1) the new hard ValueErrors on dotted/underscored tokens are an upgrade break for custom FlareAgentWithCellPipe agent ids that previously worked — deliberate and loud, but release-note it; (2) a mixed-version CJ↔subprocess pair (training venv on older nvflare) fails with "peer FQCN mismatch" — inherent to any rename, second rename in a row for this cell, also a release-note item; Security engineer: overall risk LOW
Two low-severity residuals, both independently found by all three reviewers:
Consolidated recommendation Suggested follow-ups (none blocking): add a local_fqcn= identity-binding test for underscore-token pipe children; make the empty-token fallback unique or raising; add release notes for the new token restrictions and the CJ↔subprocess version-mismatch behavior; put a compat/naming-scheme note next to the grammar in fqcn.py; and consider a connected-ancestor last-resort in _try_find_ep as the deeper routing fix. The bundled client_ctl.py error-reporting fix is correct but separable — fine to keep, worth a mention in the PR description. |
… scheme - An empty CellPipe token now raises ValueError in all branches instead of silently falling back to a shared "default" name: an empty token cannot uniquely name the cell, and a generated per-process fallback is not possible because the two ends of a pipe pair derive each other's names independently. This restores the loud failure the pre-PR code had (invalid FQCN) with a clearer message. - Pin the CP-local alias seam in a test: a CP resolving its own underscore-token pipe child parses a fabricated owner (fail closed, non-mTLS in practice) - documented rather than special-cased since the name alone cannot distinguish it from a relay alias. - Add direct unit tests for make/parse_cell_pipe_alias and relay-shape routing tests for <relay>.<site>_<token>_<mode> cells. - Document the CellPipe naming-scheme history and mixed-version behavior next to the alias grammar in fqcn.py. - Release-note the new token restrictions and the CJ/subprocess version-mismatch behavior in flare_280.rst. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@chesterxgchen Thanks for the three-angle review. The suggested follow-ups are addressed in 16916c5: 1. Empty-token fallback → now raising. Of your two options (unique or raising), a process-unique suffix is structurally impossible for CellPipe: the two ends of a pipe pair derive each other's cell names independently from the same 2. CP-local alias seam → pinned in a test. 3. Test gaps — added 4. Compat/naming-scheme note — the grammar block in 5. Release notes — 6. PR description — updated to call out the separable ccwf Deferred as a separate follow-up: the connected-ancestor last-resort in All cellnet/pipe/authenticator/ccwf suites pass (201 tests) plus black/isort/flake8 on the touched files. |
CellPipe leaves are now explicitly marked: - plain leaf "cellpipe-<token>_<mode>" for root/own-CP connections - alias leaf "cellpipe-alias-<owner>_<token>_<mode>" behind a relay This makes the alias grammar unambiguous instead of positionally gated: a plain leaf whose token contains "_" (e.g. cellpipe-ext_trainer_active) can never be misread as an alias, so identity resolution recognizes the marked alias at any depth (restoring owner resolution from distant cells, e.g. cert exchange through the server) and resolves any plain pipe leaf to the identity of the cell it is named under - which also fixes the CP-local seam where an underscore-token pipe child parsed to a fabricated owner. Stream auth accepts the bare legacy grammar only for whole-FQCN (pre-2.8 flat) origins and requires the alias marker at any depth. Tokens starting with "alias-" are rejected at construction so the plain namespace cannot collide with the alias namespace. Verified end-to-end with a POC FedAvg ext-process job (2 clients, 2 rounds, 109MB streamed model): pipes rendezvous under the new names and the job completes cleanly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Follow-up in 100e8e8: CellPipe leaves are now explicitly marked, per review discussion —
This eliminates the alias/plain ambiguity grammatically instead of containing it positionally:
Verified beyond unit tests (213 passing across cellnet/pipe/authenticator/ccwf, plus direct grammar tests): an end-to-end POC FedAvg run (2 clients, 2 rounds, ext-process ScriptRunner, 109 MB streamed model) completes cleanly with pipes rendezvousing under the new names ( |
Released 2.6/2.7 name CP/relay-connected pipes with a bare alias leaf nested under the connected cell (site-1.site-1_<job>_<mode> or relay-1.site-1_<job>_<mode>), not only as whole-FQCN root siblings. Honor the bare alias grammar at any FQCN depth in identity resolution and stream auth so an upgraded CP/relay/server still authenticates pipe cells of a pre-2.8 site. This is unambiguous with the current naming: every current pipe leaf places "~" before the mode field, so it can never parse under the bare grammar. The unified alias branch in CellIdentityResolver.resolve() now handles both alias shapes in one place (parent-qualified owner lookup first, then the owner's default identity), and the single-segment-only gate that was duplicated between identity resolution and stream auth is removed on both sides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A do_learn_task failure recorded just before the end-workflow request arrived was silently dropped: BEFORE_PULL_TASK returns early once workflow_done is set, so the job still ended FINISHED:COMPLETED. Let an error-bearing status report through even after workflow_done, and skip recording EXECUTION_EXCEPTION when the task's abort signal was triggered - an aborted task (end-of-workflow teardown) is expected to raise and is not a job failure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The np-loop-cell-pipe and pt-large-model-pass-through cases were verbatim copies in client_api.yml and ext_process_streaming.yml. Keep them only in ext_process_streaming.yml and list that file under both the client_api and ext_process_streaming groups. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Accepting the bare (unmarked) legacy alias grammar at any FQCN depth reintroduced the exact ambiguity the cellpipe~alias~ marker was added to remove: a bare <owner>_<runtime>_<mode> leaf is indistinguishable from a real cell whose name legitimately contains '_' and ends in '_active'/'_passive' (permitted by the client/relay name pattern ^[A-Za-z0-9-_]+$). That misresolved such a cell behind a relay (mTLS rejected, e.g. relay-1.east_region_active -> 'east') and let a co-located 'east' client claim the stream origin 'relay-1.east_region_active'. Restore the single-segment gate: the bare grammar is honored only for a whole-FQCN name; the explicit cellpipe~alias~ marker carries aliases at any depth (with the parent-qualified identity lookup preserved). This drops silent 2.6/2.7 nested-bare compat, which is a narrow mixed-version case and is now documented as unsupported. Also harden parse_cell_pipe_alias to reject any '~'-bearing segment under the bare grammar, making the 'current leaves never bare-parse' invariant structural rather than incidental. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A learn-task failure recorded just as the workflow ends was only delivered via the piggybacked status report on the next task pull. But the server stops consuming those reports once its control_flow returns (server_runner clears current_wf right after), so the report never reached _update_client_status and the job could still end FINISHED:COMPLETED - the exact race the earlier ccwf change targeted but did not actually close on the server side. Carry the recorded error on the synchronous _process_end_workflow reply and have ServerSideController.control_flow system_panic when a client reports one, so an already-recorded failure deterministically fails the job regardless of pull timing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This reverts the end-workflow-reply delivery added in 382c984. Review found it introduces a worse problem than it solves: the client records recoverable conditions in the same current_status.error field as genuine failures (e.g. swarm_client_ctl.py records MODEL_UNRECOGNIZED when a slow straggler's late result is benignly dropped, which is normal with min_responses < num_trainers), and the server system_panics on any reported error. Delivering that error deterministically on the end-workflow reply would fail an otherwise-successful swarm job. The original race (a genuine EXECUTION_EXCEPTION recorded just as the workflow ends can be dropped by the piggyback status-report path) remains a known low-severity limitation. Fixing it safely requires first separating recoverable conditions from fatal errors in the status model so that only genuine failures fail the job; that is the prerequisite and belongs with the ccwf error-model owner. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Revert the BEFORE_PULL_TASK gate to "if self.workflow_done: return". Continuing to report a recorded current_status.error after the workflow ends carries the same false-positive as the reverted end-workflow-reply delivery, only racier: current_status.error conflates recoverable conditions (e.g. a swarm straggler's dropped MODEL_UNRECOGNIZED submission) with fatal failures, and the server system_panics on any reported error. During the server's end-workflow reply-wait current_wf is still set, so a post-workflow_done pull can be consumed and fail an otherwise-successful job. It also delivers genuine end-boundary errors only racily. Genuine failures are still reported on the normal pull path while the workflow is live (the _do_learn fix), which is unchanged. Reliably delivering a failure that races end-of-workflow requires first separating recoverable conditions from fatal errors in the ccwf status model; that is tracked separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
YuanTingHsieh
left a comment
There was a problem hiding this comment.
Version-targeting follow-up (details in the two thread replies on flare_280.rst and fqcn.py): 2.8.0 is tagged without #4801, so 2.8 shipped the legacy flat naming and this PR lands in 2.9. Concretely:
- the release-note hunk must move out of
flare_280.rstinto the 2.9 notes (file doesn't exist yet), with its internal "2.8 CJ" / "2.6/2.7 forms" / "pre-2.8" references shifted accordingly; - five code comments say "pre-2.8" / "through 2.7" where legacy actually extends through 2.8 — three in
fqcn.py(see thread reply there) and the two marked inline below; - the compat consequence is now about the current GA release: a fully-2.8.0 site with relay-connected CellPipes loses pipe identity resolution through a relay upgraded to 2.9 ("upgrade a site and its relay together" — worth stating in the 2.9 note that this covers 2.8 sites).
Everything else from the earlier review rounds checks out at this head: the routing fix, the ~ grammar (verified contained — only CellPipe produces ~; base FQN still rejects it for site names; no URL/path/split leakage), the identity/auth matrix (no spoofing path, fail-closed on malformed aliases), the empty-token fail-fast (no in-tree caller regresses), and the ccwf _do_learn hunk. Remaining nice-to-have: an identity-side negative test that a nested legacy bare alias is NOT owner-resolved (e.g. resolve("relay-1.site-1_job_active") != "site-1") — one line, and it pins exactly the compat policy the release note documents.
|
/build |
Summary
~-delimited leaf segments such assite-1.cellpipe~plain~<jobid>~active(or<relay>.cellpipe~alias~<site>~<jobid>~activebehind a relay), avoiding the unconnected runtime-token parent introduced by Name CellPipe cells hierarchically for mTLS identity resolution #4801.server.<jobid>through the client CP.<site>_<token>_<mode>) for root-connected pipes. The 2.6/2.7 bare aliases nested under a CP or relay are intentionally not recognized because an unmarked leaf inside a longer FQCN is indistinguishable from a real cell of that name; upgrade a site and its relay together.auth_identityoverrides (e.g.relay-1.site-1-> custom cert CN) and resolves the explicit alias marker before generic prefix mappings, so a relay identity cannot shadow the alias owner.Root Cause
PR #4801 changed CellPipe cells to hierarchical names such as
site-1.<jobid>.active. The subprocess cell connects physically tosite-1, but its FQCN parent becamesite-1.<jobid>, which is not connected. Cross-family routing toserver.<jobid>then fell back to the missing FQCN parent and returnedTARGET_UNREACHABLE.Validation
~/nvflare-env/3.12/bin/python -m pytest tests/unit_test/fuel/utils/pipe/cell_pipe_test.py tests/unit_test/fuel/f3/cellnet/ tests/unit_test/private/fed/authenticator_test.py tests/unit_test/app_common/ccwf/client_ctl_test.py -q~/nvflare-env/3.12/bin/python -m black --check ...on touched files~/nvflare-env/3.12/bin/python -m isort --check-only ...on touched files~/nvflare-env/3.12/bin/python -m flake8 ...on touched filesNote: repo-wide
PATH="$HOME/nvflare-env/3.12/bin:$PATH" ./runtest.sh -swas attempted, but failed on unrelated checked-inexamples/.../node_modules/.../flatted.pyfiles that black wants to reformat.NVBug: 6371056
Also included (separable)
ClientSideController._do_learnnow recordsReturnCode.EXECUTION_EXCEPTIONwhendo_learn_taskraises before the task is aborted, so the server can receive the failure on the normal task-pull path while the workflow is live. This also fixes the brokenself.logger.log(msg)call and snapshots abort state before logging can yield. Status reporting stops afterworkflow_done: reliably delivering a genuine failure that races the end-workflow boundary requires separating fatal errors from recoverable conditions in the ccwf status model and remains a follow-up.np-loop-cell-pipeandpt-large-model-pass-throughcases now live only inext_process_streaming.yml, which is listed under both theclient_apiandext_process_streaminggroups (previously verbatim copies in two files).