Skip to content

Commit bc40635

Browse files
jpmorardjpmorard
andauthored
Fix ORCHESTRATE refresh workers path growth (#649)
Co-authored-by: jpmorard <focus@thalesgroup.com>
1 parent 53c383a commit bc40635

2 files changed

Lines changed: 132 additions & 8 deletions

File tree

src/agilab/orchestrate/orchestrate_cluster.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,16 @@ def _workflow_sessions_root(cluster_share: Path, user: Any, workflow_name: Any)
419419
return _workflow_user_root(cluster_share, user) / safe_workflow
420420

421421

422+
def _workflow_cluster_share_root(cluster_share: Path, *, user: Any, workflow_name: Any) -> Path:
423+
safe_user = _safe_workflow_component(user, "local-user")
424+
safe_workflow = _safe_workflow_component(workflow_name, "default")
425+
parts = cluster_share.parts
426+
for index in range(len(parts) - 3, -1, -1):
427+
if parts[index] == safe_user and parts[index + 1] == safe_workflow:
428+
return Path(*parts[: index + 1])
429+
return cluster_share
430+
431+
422432
def _list_workflow_sessions(sessions_root: Path) -> list[Path]:
423433
try:
424434
entries = [path for path in sessions_root.iterdir() if path.is_dir()]
@@ -471,6 +481,11 @@ def _resolve_workflow_session_path(
471481
def _workflow_data_path_text(cluster_share: Path, session_path: Path, env: Any) -> str:
472482
cluster_share_setting = _env_cluster_share_setting(env)
473483
if cluster_share_setting:
484+
setting_path = _resolve_env_relative_path(cluster_share_setting, env)
485+
if setting_path is not None and (
486+
setting_path == session_path or _path_is_within(setting_path, session_path)
487+
):
488+
return _home_relative_share_text(session_path, env) or str(session_path)
474489
try:
475490
suffix = session_path.relative_to(cluster_share)
476491
except ValueError:
@@ -1465,14 +1480,23 @@ def render_cluster_settings_ui(env: Any, deps: OrchestrateClusterDeps, *, show_r
14651480
and cluster_share_candidate.is_dir()
14661481
and os.access(cluster_share_candidate, os.W_OK)
14671482
)
1483+
workflow_cluster_share_candidate = (
1484+
_workflow_cluster_share_root(
1485+
cluster_share_candidate,
1486+
user=sanitized_user,
1487+
workflow_name=workflow_id,
1488+
)
1489+
if cluster_share_candidate is not None
1490+
else None
1491+
)
14681492
workflow_session_options: list[str] = []
1469-
if cluster_share_ready and cluster_share_candidate is not None:
1493+
if cluster_share_ready and workflow_cluster_share_candidate is not None:
14701494
workflow_session_options = _workflow_session_names(
1471-
_workflow_sessions_root(cluster_share_candidate, sanitized_user, workflow_id)
1495+
_workflow_sessions_root(workflow_cluster_share_candidate, sanitized_user, workflow_id)
14721496
)
14731497
try:
14741498
workflow_session_path, workflow_session, workflow_policy = _resolve_workflow_session_path(
1475-
cluster_share_candidate,
1499+
workflow_cluster_share_candidate,
14761500
user=sanitized_user,
14771501
workflow_name=workflow_id,
14781502
policy=workflow_policy,
@@ -1484,7 +1508,7 @@ def render_cluster_settings_ui(env: Any, deps: OrchestrateClusterDeps, *, show_r
14841508
st.error(f"Could not prepare workflow session under `{cluster_share_candidate}`: {exc}")
14851509
else:
14861510
workflow_workers_data_path = _workflow_data_path_text(
1487-
cluster_share_candidate,
1511+
workflow_cluster_share_candidate,
14881512
workflow_session_path,
14891513
env,
14901514
)
@@ -1530,7 +1554,7 @@ def render_cluster_settings_ui(env: Any, deps: OrchestrateClusterDeps, *, show_r
15301554
st.session_state.pop(workflow_existing_session_key, None)
15311555
st.info(
15321556
"No existing workflow session directories found under "
1533-
f"`{_workflow_sessions_root(cluster_share_candidate, sanitized_user, workflow_id)}`."
1557+
f"`{_workflow_sessions_root(workflow_cluster_share_candidate, sanitized_user, workflow_id)}`."
15341558
)
15351559
else:
15361560
st.session_state.pop(workflow_existing_session_key, None)
@@ -1547,10 +1571,10 @@ def render_cluster_settings_ui(env: Any, deps: OrchestrateClusterDeps, *, show_r
15471571
),
15481572
)
15491573
workflow_session = _safe_workflow_component(workflow_session_input, "")
1550-
if cluster_share_ready and cluster_share_candidate is not None and not workflow_session_error_shown:
1574+
if cluster_share_ready and workflow_cluster_share_candidate is not None and not workflow_session_error_shown:
15511575
try:
15521576
workflow_session_path, workflow_session, workflow_policy = _resolve_workflow_session_path(
1553-
cluster_share_candidate,
1577+
workflow_cluster_share_candidate,
15541578
user=sanitized_user,
15551579
workflow_name=workflow_id,
15561580
policy=workflow_policy,
@@ -1560,7 +1584,7 @@ def render_cluster_settings_ui(env: Any, deps: OrchestrateClusterDeps, *, show_r
15601584
st.error(f"Could not prepare workflow session under `{cluster_share_candidate}`: {exc}")
15611585
else:
15621586
workflow_workers_data_path = _workflow_data_path_text(
1563-
cluster_share_candidate,
1587+
workflow_cluster_share_candidate,
15641588
workflow_session_path,
15651589
env,
15661590
)

test/test_orchestrate_cluster.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,22 @@ def test_workflow_session_path_regression_uses_workflow_session_root_semantic(tm
632632
assert "workers" not in path.parts
633633
assert "flight_telemetry_project" not in path.parts
634634
assert "flight_telemetry" not in path.parts
635+
assert (
636+
orchestrate_cluster._workflow_cluster_share_root(
637+
path,
638+
user="agi",
639+
workflow_name="workflows",
640+
)
641+
== share
642+
)
643+
assert (
644+
orchestrate_cluster._workflow_cluster_share_root(
645+
share / "workflows",
646+
user="agi",
647+
workflow_name="workflows",
648+
)
649+
== share / "workflows"
650+
)
635651

636652

637653
def test_workflow_workers_data_path_regression_does_not_duplicate_module_dataset(tmp_path):
@@ -2025,6 +2041,90 @@ def test_render_cluster_settings_ui_refresh_replaces_stale_lan_discovery_state(m
20252041
assert any("LAN discovery refreshed" in info for info in fake_st.infos)
20262042

20272043

2044+
def test_render_cluster_settings_ui_refresh_keeps_session_scoped_share_root(monkeypatch, tmp_path):
2045+
app_name = "flight_trajectory_project"
2046+
session_id = "20260618T185326Z-4ffeb367"
2047+
widget_keys = orchestrate_cluster.cluster_widget_keys(app_name)
2048+
session_share = tmp_path / "clustershare" / "agi" / "workflows" / session_id
2049+
session_share.mkdir(parents=True)
2050+
refresh_calls = []
2051+
fake_st = _FakeStreamlit(
2052+
widget_values={
2053+
widget_keys["cluster_enabled"]: True,
2054+
widget_keys["cython"]: False,
2055+
widget_keys["pool"]: False,
2056+
widget_keys["rapids"]: False,
2057+
widget_keys["use_key"]: True,
2058+
widget_keys["workflow_session"]: session_id,
2059+
},
2060+
button_values={
2061+
orchestrate_cluster._lan_discovery_refresh_key(app_name): True,
2062+
},
2063+
session_state={
2064+
"app_settings": {
2065+
"cluster": {
2066+
"cluster_enabled": True,
2067+
"scheduler": "10.0.0.10:8786",
2068+
"workers": {"10.0.0.11": 2},
2069+
"workers_data_path": f"clustershare/agi/workflows/{session_id}",
2070+
"workflow_session": session_id,
2071+
}
2072+
},
2073+
widget_keys["scheduler"]: "10.0.0.10:8786",
2074+
widget_keys["user"]: "agi",
2075+
widget_keys["workers"]: '{"10.0.0.11": 2}',
2076+
widget_keys["workers_data_path"]: f"clustershare/agi/workflows/{session_id}",
2077+
widget_keys["workflow_session"]: session_id,
2078+
"benchmark": False,
2079+
},
2080+
)
2081+
monkeypatch.setattr(orchestrate_cluster, "st", fake_st)
2082+
monkeypatch.setattr(
2083+
orchestrate_cluster,
2084+
"_lan_discovery_cluster_defaults",
2085+
lambda *_, **__: {
2086+
"scheduler": "192.168.3.103:8786",
2087+
"workers": {"192.168.3.35": 1},
2088+
},
2089+
)
2090+
monkeypatch.setattr(
2091+
orchestrate_cluster,
2092+
"_refresh_lan_discovery_cache",
2093+
lambda cache_path, **kwargs: refresh_calls.append((cache_path, kwargs))
2094+
or (True, "LAN discovery refreshed: 1 node(s), 1 ready."),
2095+
)
2096+
deps = orchestrate_cluster.OrchestrateClusterDeps(
2097+
parse_and_validate_scheduler=lambda raw: raw,
2098+
parse_and_validate_workers=lambda raw: {"192.168.3.35": 1} if "192.168.3.35" in raw else None,
2099+
write_app_settings_toml=lambda _path, settings: settings,
2100+
clear_load_toml_cache=lambda: None,
2101+
set_env_var=lambda _key, _value: None,
2102+
agi_env_envars={},
2103+
)
2104+
env = SimpleNamespace(
2105+
app=app_name,
2106+
home_abs=tmp_path,
2107+
is_managed_pc=False,
2108+
AGI_CLUSTER_SHARE=str(session_share),
2109+
agi_share_path=Path(f"clustershare/agi/workflows/{session_id}"),
2110+
share_root_path=lambda: session_share,
2111+
user="agi",
2112+
password=None,
2113+
ssh_key_path=None,
2114+
app_settings_file=tmp_path / "app_settings.toml",
2115+
)
2116+
2117+
orchestrate_cluster.render_cluster_settings_ui(env, deps)
2118+
2119+
cluster = fake_st.session_state.app_settings["cluster"]
2120+
expected = f"clustershare/agi/workflows/{session_id}"
2121+
duplicated = f"{expected}/agi/workflows/{session_id}"
2122+
assert cluster["workers_data_path"] == expected
2123+
assert fake_st.session_state[widget_keys["workers_data_path"]] == expected
2124+
assert duplicated not in cluster["workers_data_path"]
2125+
assert refresh_calls
2126+
2127+
20282128
def test_render_cluster_settings_ui_builds_advisory_cluster_plan_without_applying(monkeypatch, tmp_path):
20292129
app_name = "demo_project"
20302130
widget_keys = orchestrate_cluster.cluster_widget_keys(app_name)

0 commit comments

Comments
 (0)