Skip to content

Commit 57026f9

Browse files
Add toggles to disable the interactivity endpoints (#264)
* Add toggles to disable the interactivity endpoints * add tests * resolve copilot comments
1 parent a5d8570 commit 57026f9

2 files changed

Lines changed: 264 additions & 0 deletions

File tree

autosubmit_api/routers/v4/runners.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,31 @@ def get_runner_configuration_profiles(
5151
return profiles
5252

5353

54+
@router.get("/configuration/endpoints", name="Get runner configuration endpoints")
55+
def get_runner_configuration_endpoints(
56+
user_id: Optional[str] = Depends(auth_token_dependency()),
57+
) -> Dict[str, Any]:
58+
"""
59+
Get the runner configuration endpoints from the configuration file.
60+
"""
61+
config = read_config_file()
62+
endpoints = config.get("RUNNER_CONFIGURATION", {}).get("ENDPOINTS", {})
63+
if not isinstance(endpoints, dict):
64+
endpoints = {}
65+
66+
default_endpoints = {
67+
"SET_JOB_STATUS": {"ENABLED": True},
68+
"CREATE_EXPERIMENT": {"ENABLED": True},
69+
"RUNNER_RUN": {"ENABLED": True},
70+
}
71+
72+
merged_endpoints = {}
73+
for key in set(default_endpoints.keys()):
74+
merged_endpoints[key] = {**default_endpoints[key], **endpoints.get(key, {})}
75+
76+
return merged_endpoints
77+
78+
5479
class RunnerEndpointBody(BaseModel):
5580
profile_name: str
5681
profile_params: Optional[Dict[str, Any]] = None
@@ -74,6 +99,13 @@ class SetJobStatusBody(RunnerEndpointBody):
7499
command_params: SetJobStatusCmdParams
75100

76101

102+
def _endpoint_enabled(endpoint_name: str) -> bool:
103+
config = read_config_file()
104+
endpoints = config.get("RUNNER_CONFIGURATION", {}).get("ENDPOINTS", {})
105+
endpoint_config = endpoints.get(endpoint_name, {})
106+
return endpoint_config.get("ENABLED", True)
107+
108+
77109
@router.post("/command/set-job-status", name="Set job status for an experiment")
78110
async def set_job_status(
79111
body: SetJobStatusBody,
@@ -82,6 +114,12 @@ async def set_job_status(
82114
"""
83115
Set the job status for an experiment using the specified runner profile.
84116
"""
117+
if not _endpoint_enabled("SET_JOB_STATUS"):
118+
raise HTTPException(
119+
status_code=403,
120+
detail="The set-job-status endpoint is currently disabled.",
121+
)
122+
85123
expid = body.expid
86124
command_params = body.command_params
87125

@@ -128,6 +166,12 @@ async def run_experiment(
128166
"""
129167
Run an experiment using the specified runner profile.
130168
"""
169+
if not _endpoint_enabled("RUNNER_RUN"):
170+
raise HTTPException(
171+
status_code=403,
172+
detail="The run-experiment endpoint is currently disabled.",
173+
)
174+
131175
expid = body.expid
132176

133177
logger.info(f"Starting experiment {expid} using profile {body.profile_name}")
@@ -174,6 +218,12 @@ async def get_runner_run_status(
174218
"""
175219
Get the status of the runner run for a given experiment ID.
176220
"""
221+
if not _endpoint_enabled("RUNNER_RUN"):
222+
raise HTTPException(
223+
status_code=403,
224+
detail="The get-runner-run-status endpoint is currently disabled.",
225+
)
226+
177227
expid = body.expid
178228

179229
logger.info(f"Getting runner run status for experiment {expid}")
@@ -215,6 +265,12 @@ async def stop_experiment(
215265
"""
216266
Stop an experiment using the specified runner profile.
217267
"""
268+
if not _endpoint_enabled("RUNNER_RUN"):
269+
raise HTTPException(
270+
status_code=403,
271+
detail="The stop-experiment endpoint is currently disabled.",
272+
)
273+
218274
expid = body.expid
219275

220276
logger.info(f"Stopping experiment {expid}")
@@ -256,6 +312,12 @@ async def create_experiment(
256312
"""
257313
Create an experiment using the specified runner profile.
258314
"""
315+
if not _endpoint_enabled("CREATE_EXPERIMENT"):
316+
raise HTTPException(
317+
status_code=403,
318+
detail="The create-experiment endpoint is currently disabled.",
319+
)
320+
259321
command_params = body.command_params
260322

261323
logger.info(f"Creating experiment using profile {body.profile_name}")

tests/test_endpoints_v4.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,37 @@ def test_invalid_profile(self, fixture_fastapi_client: TestClient):
515515

516516
assert response.status_code != 200
517517

518+
def test_disabled_endpoint(self, fixture_fastapi_client: TestClient):
519+
with patch(
520+
"autosubmit_api.routers.v4.runners.read_config_file"
521+
) as mock_read_config:
522+
mock_read_config.return_value = {
523+
"RUNNER_CONFIGURATION": {
524+
"PROFILES": {
525+
"MY_PROFILE": {
526+
"RUNNER_TYPE": "LOCAL",
527+
"MODULE_LOADER_TYPE": "NO_MODULE",
528+
}
529+
},
530+
"ENDPOINTS": {"SET_JOB_STATUS": {"ENABLED": False}},
531+
},
532+
}
533+
534+
response = fixture_fastapi_client.post(
535+
self.endpoint,
536+
json={
537+
"expid": "test_expid",
538+
"profile_name": "MY_PROFILE",
539+
"command_params": {
540+
"final_status": "COMPLETED",
541+
"job_names_list": ["JOB1", "JOB2"],
542+
},
543+
},
544+
)
545+
546+
assert response.status_code == 403
547+
assert "disabled" in response.json()["error_message"]
548+
518549
def test_valid_ssh_request(self, fixture_fastapi_client: TestClient):
519550
# Mock read_config_file to include SSH_AUTOSUBMIT_DEV profile
520551
# and get_runner to return a mock runner
@@ -567,6 +598,27 @@ def test_valid_ssh_request(self, fixture_fastapi_client: TestClient):
567598
class TestRunnerRunExperiment:
568599
endpoint = "/v4/runners/command/run-experiment"
569600

601+
def test_disabled_endpoint(self, fixture_fastapi_client: TestClient):
602+
with patch(
603+
"autosubmit_api.routers.v4.runners.read_config_file"
604+
) as mock_read_config:
605+
mock_read_config.return_value = {
606+
"RUNNER_CONFIGURATION": {
607+
"ENDPOINTS": {"RUNNER_RUN": {"ENABLED": False}},
608+
},
609+
}
610+
611+
response = fixture_fastapi_client.post(
612+
self.endpoint,
613+
json={
614+
"expid": "test_expid",
615+
"profile_name": "ANY_PROFILE",
616+
},
617+
)
618+
619+
assert response.status_code == 403
620+
assert "disabled" in response.json()["error_message"]
621+
570622
def test_valid_ssh_request(self, fixture_fastapi_client: TestClient):
571623
with (
572624
patch(
@@ -612,6 +664,24 @@ def test_valid_ssh_request(self, fixture_fastapi_client: TestClient):
612664
class TestRunnerGetRunnerRunStatus:
613665
endpoint = "/v4/runners/command/get-runner-run-status"
614666

667+
def test_disabled_endpoint(self, fixture_fastapi_client: TestClient):
668+
with patch(
669+
"autosubmit_api.routers.v4.runners.read_config_file"
670+
) as mock_read_config:
671+
mock_read_config.return_value = {
672+
"RUNNER_CONFIGURATION": {
673+
"ENDPOINTS": {"RUNNER_RUN": {"ENABLED": False}},
674+
},
675+
}
676+
677+
response = fixture_fastapi_client.post(
678+
self.endpoint,
679+
json={"expid": "test_expid"},
680+
)
681+
682+
assert response.status_code == 403
683+
assert "disabled" in response.json()["error_message"]
684+
615685
def test_valid_request(self, fixture_fastapi_client: TestClient):
616686
with patch(
617687
"autosubmit_api.routers.v4.runners.create_runner_processes_repository"
@@ -669,6 +739,24 @@ def test_no_process_found(self, fixture_fastapi_client: TestClient):
669739
class TestRunnerStopExperiment:
670740
endpoint = "/v4/runners/command/stop-experiment"
671741

742+
def test_disabled_endpoint(self, fixture_fastapi_client: TestClient):
743+
with patch(
744+
"autosubmit_api.routers.v4.runners.read_config_file"
745+
) as mock_read_config:
746+
mock_read_config.return_value = {
747+
"RUNNER_CONFIGURATION": {
748+
"ENDPOINTS": {"RUNNER_RUN": {"ENABLED": False}},
749+
},
750+
}
751+
752+
response = fixture_fastapi_client.post(
753+
self.endpoint,
754+
json={"expid": "test_expid"},
755+
)
756+
757+
assert response.status_code == 403
758+
assert "disabled" in response.json()["error_message"]
759+
672760
def test_valid_ssh_request(self, fixture_fastapi_client: TestClient):
673761
with patch(
674762
"autosubmit_api.routers.v4.runners.get_runner_from_expid"
@@ -703,6 +791,29 @@ def test_stop_failure(self, fixture_fastapi_client: TestClient):
703791
class TestRunnerCreateExperiment:
704792
endpoint = "/v4/runners/command/create-experiment"
705793

794+
def test_disabled_endpoint(self, fixture_fastapi_client: TestClient):
795+
with patch(
796+
"autosubmit_api.routers.v4.runners.read_config_file"
797+
) as mock_read_config:
798+
mock_read_config.return_value = {
799+
"RUNNER_CONFIGURATION": {
800+
"ENDPOINTS": {"CREATE_EXPERIMENT": {"ENABLED": False}},
801+
},
802+
}
803+
804+
response = fixture_fastapi_client.post(
805+
self.endpoint,
806+
json={
807+
"profile_name": "ANY_PROFILE",
808+
"command_params": {
809+
"description": "Test experiment",
810+
},
811+
},
812+
)
813+
814+
assert response.status_code == 403
815+
assert "disabled" in response.json()["error_message"]
816+
706817
def test_valid_local_request(self, fixture_fastapi_client: TestClient):
707818
with (
708819
patch(
@@ -750,3 +861,94 @@ def test_no_description(self, fixture_fastapi_client: TestClient):
750861
)
751862

752863
assert response.status_code != 200
864+
865+
866+
class TestRunnerConfigurations:
867+
@pytest.mark.parametrize(
868+
"file_content, expected",
869+
[
870+
(
871+
{},
872+
{
873+
"SET_JOB_STATUS": {"ENABLED": True},
874+
"RUNNER_RUN": {"ENABLED": True},
875+
"CREATE_EXPERIMENT": {"ENABLED": True},
876+
},
877+
),
878+
(
879+
{
880+
"RUNNER_CONFIGURATION": {
881+
"ENDPOINTS": {
882+
"SET_JOB_STATUS": {"ENABLED": False},
883+
"RUNNER_RUN": {"ENABLED": False},
884+
"CREATE_EXPERIMENT": {"ENABLED": False},
885+
}
886+
}
887+
},
888+
{
889+
"SET_JOB_STATUS": {"ENABLED": False},
890+
"RUNNER_RUN": {"ENABLED": False},
891+
"CREATE_EXPERIMENT": {"ENABLED": False},
892+
},
893+
),
894+
(
895+
{
896+
"RUNNER_CONFIGURATION": {
897+
"ENDPOINTS": {
898+
"SET_JOB_STATUS": {
899+
"ENABLED": False,
900+
"EXTRA_KEY": "foo",
901+
},
902+
"CREATE_EXPERIMENT": {"ENABLED": True},
903+
}
904+
}
905+
},
906+
{
907+
"SET_JOB_STATUS": {"ENABLED": False, "EXTRA_KEY": "foo"},
908+
"RUNNER_RUN": {"ENABLED": True},
909+
"CREATE_EXPERIMENT": {"ENABLED": True},
910+
},
911+
),
912+
(
913+
{
914+
"RUNNER_CONFIGURATION": {
915+
"ENDPOINTS": {
916+
"SET_JOB_STATUS": {
917+
"EXTRA_KEY": "foo",
918+
},
919+
}
920+
}
921+
},
922+
{
923+
"SET_JOB_STATUS": {"ENABLED": True, "EXTRA_KEY": "foo"},
924+
"RUNNER_RUN": {"ENABLED": True},
925+
"CREATE_EXPERIMENT": {"ENABLED": True},
926+
},
927+
),
928+
],
929+
)
930+
def test_endpoints_configuration(
931+
self, fixture_fastapi_client: TestClient, file_content: dict, expected: dict
932+
):
933+
endpoint = "/v4/runners/configuration/endpoints"
934+
935+
with patch(
936+
"autosubmit_api.routers.v4.runners.read_config_file"
937+
) as mock_read_config:
938+
mock_read_config.return_value = file_content
939+
940+
response = fixture_fastapi_client.get(
941+
endpoint,
942+
)
943+
944+
resp_obj: dict = response.json()
945+
946+
assert resp_obj == expected
947+
948+
# for endpoint_name in ["SET_JOB_STATUS", "RUNNER_RUN", "CREATE_EXPERIMENT"]:
949+
# assert endpoint_name in resp_obj
950+
# assert "ENABLED" in resp_obj[endpoint_name]
951+
# assert (
952+
# isinstance(resp_obj[endpoint_name]["ENABLED"], bool)
953+
# and resp_obj[endpoint_name]["ENABLED"] is True
954+
# )

0 commit comments

Comments
 (0)