Skip to content

Commit 969bbb3

Browse files
GitHKAndrei Neagu
authored andcommitted
🐛 fixes issue with interactive services removal (#2507)
* fixes issue with interactive services removal * adding regression test and fixing issue * refactor * added missin braket * reverting change * added new possible error raised during operations * changed import Co-authored-by: Andrei Neagu <[email protected]>
1 parent b2cf1fa commit 969bbb3

File tree

3 files changed

+76
-13
lines changed

3 files changed

+76
-13
lines changed

services/web/server/src/simcore_service_webserver/projects/projects_db.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@ def _find_changed_dict_keys(
182182
# FIXME: not clear when data is schema-compliant and db-compliant
183183

184184

185+
def _assemble_array_groups(user_groups: List[RowProxy]) -> str:
186+
return (
187+
"array[]::text[]"
188+
if len(user_groups) == 0
189+
else f"""array[{', '.join(f"'{group.gid}'" for group in user_groups)}]"""
190+
)
191+
192+
185193
class ProjectDBAPI:
186194
def __init__(self, app: web.Application):
187195
# TODO: shall be a weak pointer since it is also contained by app??
@@ -319,7 +327,6 @@ async def load_projects(
319327

320328
async with self.engine.acquire() as conn:
321329
user_groups: List[RowProxy] = await self.__load_user_groups(conn, user_id)
322-
groups_array = ", ".join(f"'{group.gid}'" for group in user_groups)
323330

324331
query = (
325332
select([projects])
@@ -341,7 +348,7 @@ async def load_projects(
341348
)
342349
& (
343350
sa.text(
344-
f"jsonb_exists_any(projects.access_rights, array[{groups_array}])"
351+
f"jsonb_exists_any(projects.access_rights, {_assemble_array_groups(user_groups)})"
345352
)
346353
| (projects.c.prj_owner == user_id)
347354
)
@@ -443,14 +450,15 @@ async def _get_project(
443450
user_groups: List[RowProxy] = await self.__load_user_groups(connection, user_id)
444451

445452
# NOTE: in order to use specific postgresql function jsonb_exists_any we use raw call here
453+
446454
query = textwrap.dedent(
447455
f"""\
448456
SELECT *
449457
FROM projects
450458
WHERE
451459
{"" if include_templates else "projects.type != 'TEMPLATE' AND"}
452460
uuid = '{project_uuid}'
453-
AND (jsonb_exists_any(projects.access_rights, array[{', '.join(f"'{group.gid}'" for group in user_groups)}])
461+
AND (jsonb_exists_any(projects.access_rights, {_assemble_array_groups(user_groups)})
454462
OR prj_owner = {user_id})
455463
{"FOR UPDATE" if for_update else ""}
456464
"""

services/web/server/src/simcore_service_webserver/resource_manager/garbage_collector.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
from typing import Any, Dict, List, Optional, Set, Tuple
55

66
import asyncpg.exceptions
7-
import psycopg2
87
from aiohttp import web
98
from aiopg.sa.result import RowProxy
109
from aioredlock import Aioredlock
1110
from servicelib.utils import logged_gather
11+
from simcore_postgres_database.errors import DatabaseError
1212

1313
from .. import users_exceptions
1414
from ..db_models import GroupType
@@ -37,7 +37,11 @@
3737
from .registry import RedisResourceRegistry, get_registry
3838

3939
logger = logging.getLogger(__name__)
40-
database_errors = (psycopg2.DatabaseError, asyncpg.exceptions.PostgresError)
40+
database_errors = (
41+
DatabaseError,
42+
asyncpg.exceptions.PostgresError,
43+
ProjectNotFoundError,
44+
)
4145

4246
TASK_NAME = f"{__name__}.collect_garbage_periodically"
4347
TASK_CONFIG = f"{TASK_NAME}.config"
@@ -264,11 +268,22 @@ async def remove_disconnected_user_resources(
264268
if resource_name == "project_id":
265269
# inform that the project can be closed on the backend side
266270
#
267-
await remove_project_interactive_services(
268-
user_id=int(dead_key["user_id"]),
269-
project_uuid=resource_value,
270-
app=app,
271-
)
271+
try:
272+
await remove_project_interactive_services(
273+
user_id=int(dead_key["user_id"]),
274+
project_uuid=resource_value,
275+
app=app,
276+
)
277+
except ProjectNotFoundError as err:
278+
logger.warning(
279+
(
280+
"Could not remove project interactive services user_id=%s "
281+
"project_uuid=%s. Check the logs above for details [%s]"
282+
),
283+
user_id,
284+
resource_value,
285+
err,
286+
)
272287

273288
# ONLY GUESTS: if this user was a GUEST also remove it from the database
274289
# with the only associated project owned

services/web/server/tests/unit/with_dbs/10/test_resource_manager.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import asyncio
77
import json
88
import logging
9-
from asyncio import Future, sleep
9+
from asyncio import Future
1010
from copy import deepcopy
1111
from typing import Any, Callable, Dict
1212
from unittest.mock import call
@@ -20,8 +20,6 @@
2020
from aioredis import Redis
2121
from aioresponses import aioresponses
2222
from pytest_mock.plugin import MockerFixture
23-
from tenacity import after_log, retry_if_exception_type, stop_after_attempt, wait_fixed
24-
2523
from pytest_simcore.helpers.utils_assert import assert_status
2624
from pytest_simcore.helpers.utils_projects import NewProject
2725
from servicelib.application import create_safe_application
@@ -30,6 +28,11 @@
3028
from simcore_service_webserver.director_v2 import setup_director_v2
3129
from simcore_service_webserver.login import setup_login
3230
from simcore_service_webserver.projects import setup_projects
31+
from simcore_service_webserver.projects.projects_api import (
32+
delete_project_from_db,
33+
remove_project_interactive_services,
34+
)
35+
from simcore_service_webserver.projects.projects_exceptions import ProjectNotFoundError
3336
from simcore_service_webserver.resource_manager import (
3437
config,
3538
garbage_collector,
@@ -46,6 +49,8 @@
4649
from simcore_service_webserver.socketio import setup_socketio
4750
from simcore_service_webserver.socketio.events import SOCKET_IO_PROJECT_UPDATED_EVENT
4851
from simcore_service_webserver.users import setup_users
52+
from simcore_service_webserver.users_api import delete_user
53+
from tenacity import after_log, retry_if_exception_type, stop_after_attempt, wait_fixed
4954

5055
logger = logging.getLogger(__name__)
5156

@@ -71,6 +76,14 @@ def mock_garbage_collector_task(mocker):
7176
)
7277

7378

79+
@pytest.fixture
80+
def mock_delete_data_folders_for_project(mocker):
81+
mocker.patch(
82+
"simcore_service_webserver.projects.projects_api.delete_data_folders_of_project",
83+
return_value=None,
84+
)
85+
86+
7487
@pytest.fixture
7588
def client(
7689
mock_garbage_collector_task,
@@ -707,3 +720,30 @@ async def test_websocket_disconnected_remove_or_maintain_files_based_on_role(
707720
storage_subsystem_mock[1].assert_not_called()
708721
# make sure `delete_user` not called
709722
# asyncpg_storage_system_mock.assert_not_called()
723+
724+
725+
@pytest.mark.parametrize("user_role", [UserRole.USER, UserRole.TESTER, UserRole.GUEST])
726+
async def test_regression_removing_unexisting_user(
727+
client,
728+
logged_user,
729+
empty_user_project,
730+
user_role,
731+
mock_delete_data_folders_for_project,
732+
) -> None:
733+
# regression test for https://github.com/ITISFoundation/osparc-simcore/issues/2504
734+
735+
# remove project
736+
await delete_project_from_db(
737+
app=client.server.app,
738+
project_uuid=empty_user_project["uuid"],
739+
user_id=logged_user["id"],
740+
)
741+
# remove user
742+
await delete_user(app=client.server.app, user_id=logged_user["id"])
743+
744+
with pytest.raises(ProjectNotFoundError):
745+
await remove_project_interactive_services(
746+
user_id=logged_user["id"],
747+
project_uuid=empty_user_project["uuid"],
748+
app=client.server.app,
749+
)

0 commit comments

Comments
 (0)