Skip to content

Commit 03f7f0f

Browse files
authored
Merge branch 'master' into feature/daraga-doropa
2 parents 10ebce4 + 4eeaa5c commit 03f7f0f

File tree

6 files changed

+116
-28
lines changed

6 files changed

+116
-28
lines changed

services/storage/src/simcore_service_storage/db_file_meta_data.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def _list_filter_with_partial_file_id_stmt(
7272
file_id_prefix: str | None,
7373
partial_file_id: str | None,
7474
sha256_checksum: SHA256Str | None,
75-
only_files: bool,
75+
is_directory: bool | None,
7676
limit: int | None = None,
7777
offset: int | None = None,
7878
):
@@ -98,8 +98,8 @@ def _list_filter_with_partial_file_id_stmt(
9898
conditions.append(file_meta_data.c.file_id.startswith(file_id_prefix))
9999
if partial_file_id:
100100
conditions.append(file_meta_data.c.file_id.ilike(f"%{partial_file_id}%"))
101-
if only_files:
102-
conditions.append(file_meta_data.c.is_directory.is_(False))
101+
if is_directory is not None:
102+
conditions.append(file_meta_data.c.is_directory.is_(is_directory))
103103
if sha256_checksum:
104104
conditions.append(file_meta_data.c.sha256_checksum == sha256_checksum)
105105

@@ -119,7 +119,7 @@ async def list_filter_with_partial_file_id(
119119
file_id_prefix: str | None,
120120
partial_file_id: str | None,
121121
sha256_checksum: SHA256Str | None,
122-
only_files: bool,
122+
is_directory: bool | None,
123123
limit: int | None = None,
124124
offset: int | None = None,
125125
) -> list[FileMetaDataAtDB]:
@@ -129,7 +129,7 @@ async def list_filter_with_partial_file_id(
129129
file_id_prefix=file_id_prefix,
130130
partial_file_id=partial_file_id,
131131
sha256_checksum=sha256_checksum,
132-
only_files=only_files,
132+
is_directory=is_directory,
133133
limit=limit,
134134
offset=offset,
135135
)

services/storage/src/simcore_service_storage/simcore_s3_dsm.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
UploadedBytesTransferredCallback,
2222
)
2323
from models_library.api_schemas_storage import (
24+
UNDEFINED_SIZE,
2425
UNDEFINED_SIZE_TYPE,
2526
LinkType,
2627
S3BucketName,
@@ -79,7 +80,11 @@
7980
from .s3 import get_s3_client
8081
from .s3_utils import S3TransferDataCB, update_task_progress
8182
from .settings import Settings
82-
from .simcore_s3_dsm_utils import expand_directory, get_directory_file_id
83+
from .simcore_s3_dsm_utils import (
84+
compute_file_id_prefix,
85+
expand_directory,
86+
get_directory_file_id,
87+
)
8388
from .utils import (
8489
convert_db_to_model,
8590
download_to_file_or_raise,
@@ -180,8 +185,8 @@ async def list_files( # noqa C901
180185
user_id=uid, project_ids=accessible_projects_ids
181186
),
182187
file_id_prefix=None,
188+
is_directory=None,
183189
partial_file_id=uuid_filter,
184-
only_files=False,
185190
sha256_checksum=None,
186191
)
187192

@@ -523,22 +528,32 @@ async def delete_file(
523528
if not can.delete:
524529
raise FileAccessRightError(access_right="delete", file_id=file_id)
525530

526-
with suppress(FileMetaDataNotFoundError):
527-
# NOTE: deleting might be slow, so better ensure we release the connection
528-
async with self.engine.acquire() as conn:
529-
file: FileMetaDataAtDB = await db_file_meta_data.get(
530-
conn, TypeAdapter(SimcoreS3FileID).validate_python(file_id)
531-
)
531+
try:
532532
await get_s3_client(self.app).delete_objects_recursively(
533-
bucket=file.bucket_name,
534-
prefix=(
535-
ensure_ends_with(file.file_id, "/")
536-
if file.is_directory
537-
else file.file_id
538-
),
533+
bucket=self.simcore_bucket_name,
534+
prefix=file_id,
539535
)
540-
async with self.engine.acquire() as conn:
541-
await db_file_meta_data.delete(conn, [file.file_id])
536+
except S3KeyNotFoundError:
537+
_logger.warning("File %s not found in S3", file_id)
538+
# we still need to clean up the database entry (it exists)
539+
# and to invalidate the size of the parent directory
540+
541+
async with self.engine.acquire() as conn:
542+
await db_file_meta_data.delete(conn, [file_id])
543+
544+
if parent_dir_fmds := await db_file_meta_data.list_filter_with_partial_file_id(
545+
conn,
546+
user_or_project_filter=UserOrProjectFilter(
547+
user_id=user_id, project_ids=[]
548+
),
549+
file_id_prefix=compute_file_id_prefix(file_id, 2),
550+
partial_file_id=None,
551+
is_directory=True,
552+
sha256_checksum=None,
553+
):
554+
parent_dir_fmd = max(parent_dir_fmds, key=lambda fmd: len(fmd.file_id))
555+
parent_dir_fmd.file_size = UNDEFINED_SIZE
556+
await db_file_meta_data.upsert(conn, parent_dir_fmd)
542557

543558
async def delete_project_simcore_s3(
544559
self, user_id: UserID, project_id: ProjectID, node_id: NodeID | None = None
@@ -738,7 +753,7 @@ async def search_owned_files(
738753
),
739754
file_id_prefix=file_id_prefix,
740755
partial_file_id=None,
741-
only_files=True,
756+
is_directory=False,
742757
sha256_checksum=sha256_checksum,
743758
limit=limit,
744759
offset=offset,

services/storage/src/simcore_service_storage/simcore_s3_dsm_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,8 @@ async def _get_fmd(
119119
directory_file_id_fmd = await _get_fmd(conn, directory_file_id)
120120

121121
return directory_file_id if directory_file_id_fmd else None
122+
123+
124+
def compute_file_id_prefix(file_id: str, levels: int):
125+
components = file_id.strip("/").split("/")
126+
return "/".join(components[:levels])

services/storage/tests/unit/test_db_file_meta_data.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def _check(func_smt, **kwargs):
3131
file_id_prefix=None,
3232
partial_file_id=None,
3333
sha256_checksum=None,
34-
only_files=True,
34+
is_directory=False,
3535
)
3636
# WHERE file_meta_data.is_directory IS false ORDER BY file_meta_data.created_at ASC
3737

@@ -41,7 +41,7 @@ def _check(func_smt, **kwargs):
4141
file_id_prefix=None,
4242
partial_file_id=None,
4343
sha256_checksum=None,
44-
only_files=True,
44+
is_directory=False,
4545
)
4646
# WHERE file_meta_data.user_id = '42' AND file_meta_data.is_directory IS false ORDER BY file_meta_data.created_at ASC
4747

@@ -53,7 +53,7 @@ def _check(func_smt, **kwargs):
5353
file_id_prefix=None,
5454
partial_file_id=None,
5555
sha256_checksum=None,
56-
only_files=True,
56+
is_directory=False,
5757
)
5858
# WHERE (file_meta_data.user_id = '42' OR file_meta_data.project_id IN ('18d5'..., )) AND file_meta_data.is_directory IS false ORDER BY file_meta_data.created_at ASC
5959

@@ -65,7 +65,7 @@ def _check(func_smt, **kwargs):
6565
file_id_prefix=None,
6666
partial_file_id=None,
6767
sha256_checksum=None,
68-
only_files=True,
68+
is_directory=False,
6969
limit=10,
7070
offset=1,
7171
)
@@ -76,9 +76,9 @@ def _check(func_smt, **kwargs):
7676
_list_filter_with_partial_file_id_stmt,
7777
user_or_project_filter=UserOrProjectFilter(user_id=42, project_ids=[]),
7878
file_id_prefix=None,
79+
is_directory=None,
7980
partial_file_id="{project_id}/",
8081
sha256_checksum=None,
81-
only_files=False,
8282
)
8383

8484
# As used in SimcoreS3DataManager.search_owned_files
@@ -88,7 +88,7 @@ def _check(func_smt, **kwargs):
8888
file_id_prefix="api/",
8989
partial_file_id=None,
9090
sha256_checksum=faker.sha256(),
91-
only_files=True,
91+
is_directory=False,
9292
limit=10,
9393
offset=0,
9494
)

services/storage/tests/unit/test_handlers_files.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,7 @@ async def test_upload_file_is_directory_and_remove_content(
13451345
client: TestClient,
13461346
location_id: LocationID,
13471347
user_id: UserID,
1348+
faker: Faker,
13481349
):
13491350
FILE_SIZE_IN_DIR = TypeAdapter(ByteSize).validate_python("1Mib")
13501351
DIR_NAME = "some-dir"
@@ -1386,6 +1387,52 @@ async def test_upload_file_is_directory_and_remove_content(
13861387
)
13871388
assert len(list_of_files) == SUBDIR_COUNT * FILE_COUNT
13881389

1390+
# DELETE NOT EXISTING
1391+
1392+
assert client.app
1393+
1394+
delete_url = (
1395+
client.app.router["delete_file"]
1396+
.url_for(
1397+
location_id=f"{location_id}",
1398+
file_id=urllib.parse.quote(
1399+
"/".join(list_of_files[0].file_id.split("/")[:2]) + "/does_not_exist",
1400+
safe="",
1401+
),
1402+
)
1403+
.with_query(user_id=user_id)
1404+
)
1405+
response = await client.delete(f"{delete_url}")
1406+
_, error = await assert_status(response, status.HTTP_204_NO_CONTENT)
1407+
assert error is None
1408+
1409+
list_of_files: list[FileMetaDataGet] = await _list_files_legacy(
1410+
client, user_id, location_id, directory_file_upload
1411+
)
1412+
1413+
assert len(list_of_files) == SUBDIR_COUNT * FILE_COUNT
1414+
1415+
# DELETE ONE FILE FROM THE DIRECTORY
1416+
1417+
assert client.app
1418+
delete_url = (
1419+
client.app.router["delete_file"]
1420+
.url_for(
1421+
location_id=f"{location_id}",
1422+
file_id=urllib.parse.quote(list_of_files[0].file_id, safe=""),
1423+
)
1424+
.with_query(user_id=user_id)
1425+
)
1426+
response = await client.delete(f"{delete_url}")
1427+
_, error = await assert_status(response, status.HTTP_204_NO_CONTENT)
1428+
assert error is None
1429+
1430+
list_of_files: list[FileMetaDataGet] = await _list_files_legacy(
1431+
client, user_id, location_id, directory_file_upload
1432+
)
1433+
1434+
assert len(list_of_files) == SUBDIR_COUNT * FILE_COUNT - 1
1435+
13891436
# DIRECTORY REMOVAL
13901437

13911438
await delete_directory(directory_file_upload=directory_file_upload)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import pytest
2+
from simcore_service_storage.simcore_s3_dsm_utils import compute_file_id_prefix
3+
4+
5+
@pytest.mark.parametrize(
6+
"file_id, levels, expected",
7+
[
8+
(
9+
"b21a3b80-d578-4b33-a224-e24ee2e4966a/42b9cc07-60f5-4d29-a063-176d1467901c/my/amazing/sub/folder/with/a/file.bin",
10+
3,
11+
"b21a3b80-d578-4b33-a224-e24ee2e4966a/42b9cc07-60f5-4d29-a063-176d1467901c/my",
12+
),
13+
(
14+
"api/42b9cc07-60f5-4d29-a063-176d1467901c/my/amazing/sub/folder/with/a/file.bin",
15+
3,
16+
"api/42b9cc07-60f5-4d29-a063-176d1467901c/my",
17+
),
18+
],
19+
)
20+
def test_compute_file_id_prefix(file_id, levels, expected):
21+
assert compute_file_id_prefix(file_id, levels) == expected

0 commit comments

Comments
 (0)