Skip to content

Commit 9f18728

Browse files
committed
[2.7] Set JOB_CLIENTS before start_client_job
Restore JOB_CLIENTS metadata before start_client_job so client startup headers include participating clients. Add a unit test that asserts JOB_CLIENTS is present when start_client_job is invoked.
1 parent a65cfcc commit 9f18728

File tree

2 files changed

+33
-0
lines changed

2 files changed

+33
-0
lines changed

nvflare/private/fed/server/job_runner.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ def _start_run(self, job_id: str, job: Job, client_sites: Dict[str, DispatchInfo
262262
# job_clients is a dict of: token => Client
263263
assert isinstance(job_clients, dict)
264264
participating_clients = [c.to_dict() for c in job_clients.values()]
265+
# start_client_job serializes job.meta into request headers; make sure
266+
# JOB_CLIENTS is available before client startup.
267+
job.meta[JobMetaKey.JOB_CLIENTS] = participating_clients
265268
err = engine.start_app_on_server(fl_ctx, job=job, job_clients=job_clients)
266269
if err:
267270
raise RuntimeError(f"Could not start the server App for job: {job_id}.")

tests/unit_test/private/fed/server/job_runner_test.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,36 @@ def test_start_run_keeps_job_clients_meta_when_no_timeouts(mock_get_bool, mock_c
159159
assert job.meta[JobMetaKey.JOB_CLIENTS] == [{"name": "site-1"}, {"name": "site-2"}]
160160

161161

162+
@patch("nvflare.private.fed.server.job_runner.check_client_replies")
163+
@patch("nvflare.private.fed.server.job_runner.ConfigService.get_bool_var", return_value=True)
164+
def test_start_run_sets_job_clients_meta_before_start_client_job(mock_get_bool, mock_check_replies):
165+
mock_check_replies.return_value = []
166+
runner, fl_ctx, engine, job, _client_sites = _make_runner_inputs()
167+
168+
site1 = MagicMock()
169+
site1.name = "site-1"
170+
site1.to_dict.return_value = {"name": "site-1"}
171+
172+
site2 = MagicMock()
173+
site2.name = "site-2"
174+
site2.to_dict.return_value = {"name": "site-2"}
175+
176+
engine.get_job_clients.return_value = {"token-1": site1, "token-2": site2}
177+
178+
seen_job_clients_meta = {}
179+
180+
def _start_client_job_side_effect(passed_job, passed_client_sites, passed_fl_ctx):
181+
seen_job_clients_meta["value"] = passed_job.meta.get(JobMetaKey.JOB_CLIENTS)
182+
return [MagicMock()]
183+
184+
engine.start_client_job.side_effect = _start_client_job_side_effect
185+
186+
client_sites = {"site-1": MagicMock(), "site-2": MagicMock()}
187+
runner._start_run(job_id=job.job_id, job=job, client_sites=client_sites, fl_ctx=fl_ctx)
188+
189+
assert seen_job_clients_meta["value"] == [{"name": "site-1"}, {"name": "site-2"}]
190+
191+
162192
@patch("nvflare.private.fed.server.job_runner.check_client_replies")
163193
@patch("nvflare.private.fed.server.job_runner.ConfigService.get_bool_var", return_value=True)
164194
def test_start_run_raises_when_required_site_times_out(mock_get_bool, mock_check_replies):

0 commit comments

Comments
 (0)