From 05d57101de82bee87678fa96bbe0c7c775bbdf81 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 8 Jul 2025 14:43:14 +0200 Subject: [PATCH 1/5] for hotfix --- .../api/routes/computations.py | 2 +- .../modules/db/repositories/comp_runs.py | 16 +++++ .../test_db_repositories_comp_runs.py | 72 +++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py index dda8598ccf9..c43d7a12eab 100644 --- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py +++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py @@ -467,7 +467,7 @@ async def get_computation( last_run: CompRunsAtDB | None = None pipeline_state = RunningState.NOT_STARTED with contextlib.suppress(ComputationalRunNotFoundError): - last_run = await comp_runs_repo.get(user_id=user_id, project_id=project_id) + last_run = await comp_runs_repo.get_latest_run_by_project(project_id=project_id) pipeline_state = last_run.result _logger.debug( diff --git a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_runs.py b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_runs.py index e9a2f26ceab..bffe288049f 100644 --- a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_runs.py +++ b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_runs.py @@ -145,6 +145,22 @@ async def get( raise ComputationalRunNotFoundError return CompRunsAtDB.model_validate(row) + async def get_latest_run_by_project( + self, + project_id: ProjectID, + ) -> CompRunsAtDB: + async with pass_or_acquire_connection(self.db_engine) as conn: + result = await conn.execute( + sa.select(comp_runs) + .where(comp_runs.c.project_uuid == f"{project_id}") + .order_by(desc(comp_runs.c.run_id)) + .limit(1) + ) + row = result.one_or_none() + if not row: + raise ComputationalRunNotFoundError + return CompRunsAtDB.model_validate(row) + async def list_( self, *, diff --git a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py index 34d26ce135c..116535f34c5 100644 --- a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py +++ b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py @@ -1245,3 +1245,75 @@ async def test_list_group_by_collection_run_id_state_priority_precedence( assert len(items) == 1 collection_item = items[0] assert collection_item.state == RunningState.FAILED + + +async def test_get_with_user_id_none_across_multiple_users( + sqlalchemy_async_engine: AsyncEngine, + run_metadata: RunMetadataDict, + faker: Faker, + publish_project: Callable[[], Awaitable[PublishedProject]], + create_registered_user: Callable[..., dict[str, Any]], +): + """Test that get() with user_id=None retrieves the latest run regardless of user""" + published_project = await publish_project() + + # Create a second user + second_user = create_registered_user() + # second_user_id = UserID(faker.pyint(min_value=1000)) + + # Create comp runs for the original user + comp_run_user1_iter1 = await CompRunsRepository(sqlalchemy_async_engine).create( + user_id=published_project.user["id"], + project_id=published_project.project.uuid, + iteration=None, + metadata=run_metadata, + use_on_demand_clusters=faker.pybool(), + dag_adjacency_list=published_project.pipeline.dag_adjacency_list, + ) + + # Create comp runs for the second user (this should increment iteration) + comp_run_user2_iter2 = await CompRunsRepository(sqlalchemy_async_engine).create( + user_id=second_user["id"], + project_id=published_project.project.uuid, + iteration=None, + metadata=run_metadata, + use_on_demand_clusters=faker.pybool(), + dag_adjacency_list=published_project.pipeline.dag_adjacency_list, + ) + + # Create another run for the first user (should be iteration 3) + comp_run_user1_iter3 = await CompRunsRepository(sqlalchemy_async_engine).create( + user_id=published_project.user["id"], + project_id=published_project.project.uuid, + iteration=None, + metadata=run_metadata, + use_on_demand_clusters=faker.pybool(), + dag_adjacency_list=published_project.pipeline.dag_adjacency_list, + ) + + # Verify iterations are correct + assert comp_run_user1_iter1.iteration == 1 + assert comp_run_user2_iter2.iteration == 1 + assert comp_run_user1_iter3.iteration == 2 + + # Test get with user_id=None should return the latest run (highest iteration) + latest_run = await CompRunsRepository( + sqlalchemy_async_engine + ).get_latest_run_by_project( + project_id=published_project.project.uuid, + ) + assert latest_run == comp_run_user1_iter3 + assert latest_run.iteration == 2 + + # Test get with specific user_id still works + user1_latest = await CompRunsRepository(sqlalchemy_async_engine).get( + user_id=published_project.user["id"], + project_id=published_project.project.uuid, + ) + assert user1_latest == comp_run_user1_iter3 + + user2_latest = await CompRunsRepository(sqlalchemy_async_engine).get( + user_id=second_user["id"], + project_id=published_project.project.uuid, + ) + assert user2_latest == comp_run_user2_iter2 From bbfe283811cc5182929ede6b119bc1d6d105698c Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 8 Jul 2025 15:30:03 +0200 Subject: [PATCH 2/5] review @sanderegg --- .../comp_scheduler/test_db_repositories_comp_runs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py index 116535f34c5..31a0bba99d9 100644 --- a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py +++ b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py @@ -1247,7 +1247,7 @@ async def test_list_group_by_collection_run_id_state_priority_precedence( assert collection_item.state == RunningState.FAILED -async def test_get_with_user_id_none_across_multiple_users( +async def test_get_latest_run_by_project( sqlalchemy_async_engine: AsyncEngine, run_metadata: RunMetadataDict, faker: Faker, @@ -1269,6 +1269,7 @@ async def test_get_with_user_id_none_across_multiple_users( metadata=run_metadata, use_on_demand_clusters=faker.pybool(), dag_adjacency_list=published_project.pipeline.dag_adjacency_list, + collection_run_id=CollectionRunID(faker.uuid4()), ) # Create comp runs for the second user (this should increment iteration) @@ -1279,6 +1280,7 @@ async def test_get_with_user_id_none_across_multiple_users( metadata=run_metadata, use_on_demand_clusters=faker.pybool(), dag_adjacency_list=published_project.pipeline.dag_adjacency_list, + collection_run_id=CollectionRunID(faker.uuid4()), ) # Create another run for the first user (should be iteration 3) @@ -1289,6 +1291,7 @@ async def test_get_with_user_id_none_across_multiple_users( metadata=run_metadata, use_on_demand_clusters=faker.pybool(), dag_adjacency_list=published_project.pipeline.dag_adjacency_list, + collection_run_id=CollectionRunID(faker.uuid4()), ) # Verify iterations are correct From 6b652392993c682e4e33b961c454c2f26f12d766 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 8 Jul 2025 15:33:44 +0200 Subject: [PATCH 3/5] ... --- .../api/routes/computations.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py index c43d7a12eab..0b4a71301e0 100644 --- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py +++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py @@ -97,8 +97,8 @@ async def _check_pipeline_not_running_or_raise_409( computation: ComputationCreate, ) -> None: with contextlib.suppress(ComputationalRunNotFoundError): - last_run = await comp_runs_repo.get( - user_id=computation.user_id, project_id=computation.project_id + last_run = await comp_runs_repo.get_latest_run_by_project( + project_id=computation.project_id ) pipeline_state = last_run.result @@ -367,8 +367,8 @@ async def create_or_update_or_start_computation( # noqa: PLR0913 # pylint: disa last_run: CompRunsAtDB | None = None pipeline_state = RunningState.NOT_STARTED with contextlib.suppress(ComputationalRunNotFoundError): - last_run = await comp_runs_repo.get( - user_id=computation.user_id, project_id=computation.project_id + last_run = await comp_runs_repo.get_latest_run_by_project( + project_id=computation.project_id ) pipeline_state = last_run.result @@ -542,8 +542,8 @@ async def stop_computation( last_run: CompRunsAtDB | None = None pipeline_state = RunningState.UNKNOWN with contextlib.suppress(ComputationalRunNotFoundError): - last_run = await comp_runs_repo.get( - user_id=computation_stop.user_id, project_id=project_id + last_run = await comp_runs_repo.get_latest_run_by_project( + project_id=project_id ) pipeline_state = last_run.result if utils.is_pipeline_running(last_run.result): @@ -601,8 +601,8 @@ async def delete_computation( # check if current state allow to stop the computation pipeline_state = RunningState.UNKNOWN with contextlib.suppress(ComputationalRunNotFoundError): - last_run = await comp_runs_repo.get( - user_id=computation_stop.user_id, project_id=project_id + last_run = await comp_runs_repo.get_latest_run_by_project( + project_id=project_id ) pipeline_state = last_run.result if utils.is_pipeline_running(pipeline_state): @@ -636,8 +636,8 @@ def return_last_value(retry_state: Any) -> Any: before_sleep=before_sleep_log(_logger, logging.INFO), ) async def check_pipeline_stopped() -> bool: - last_run = await comp_runs_repo.get( - user_id=computation_stop.user_id, project_id=project_id + last_run = await comp_runs_repo.get_latest_run_by_project( + project_id=project_id ) pipeline_state = last_run.result return utils.is_pipeline_stopped(pipeline_state) From 57eb2c8187dc54633fb78d0dc1c2199b980916cd Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 8 Jul 2025 15:35:07 +0200 Subject: [PATCH 4/5] review @pcrespov --- .../with_dbs/comp_scheduler/test_db_repositories_comp_runs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py index 31a0bba99d9..e499bbd0c54 100644 --- a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py +++ b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py @@ -1259,7 +1259,6 @@ async def test_get_latest_run_by_project( # Create a second user second_user = create_registered_user() - # second_user_id = UserID(faker.pyint(min_value=1000)) # Create comp runs for the original user comp_run_user1_iter1 = await CompRunsRepository(sqlalchemy_async_engine).create( From 67c24e6f1e7d3a87b2917ac674c9de0eab455dc7 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 8 Jul 2025 16:34:05 +0200 Subject: [PATCH 5/5] fix --- .../with_dbs/comp_scheduler/test_db_repositories_comp_runs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py index e499bbd0c54..409b7355adf 100644 --- a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py +++ b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_db_repositories_comp_runs.py @@ -1253,6 +1253,7 @@ async def test_get_latest_run_by_project( faker: Faker, publish_project: Callable[[], Awaitable[PublishedProject]], create_registered_user: Callable[..., dict[str, Any]], + with_product: dict[str, Any], ): """Test that get() with user_id=None retrieves the latest run regardless of user""" published_project = await publish_project()