Skip to content

Commit 953dc6e

Browse files
committed
first version
1 parent 238a03b commit 953dc6e

File tree

3 files changed

+83
-43
lines changed

3 files changed

+83
-43
lines changed

services/storage/src/simcore_service_storage/simcore_s3_dsm.py

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
StorageFileID,
3535
)
3636
from models_library.users import UserID
37-
from pydantic import AnyUrl, ByteSize, NonNegativeInt, TypeAdapter
37+
from pydantic import AnyUrl, ByteSize, NonNegativeInt, TypeAdapter, ValidationError
3838
from servicelib.aiohttp.long_running_tasks.server import TaskProgress
3939
from servicelib.fastapi.client_session import get_client_session
4040
from servicelib.logging_utils import log_context
@@ -81,6 +81,7 @@
8181
from .utils.simcore_s3_dsm_utils import (
8282
compute_file_id_prefix,
8383
expand_directory,
84+
get_accessible_project_ids,
8485
get_directory_file_id,
8586
list_child_paths_from_repository,
8687
list_child_paths_from_s3,
@@ -191,17 +192,9 @@ async def list_paths(
191192
project_id = ProjectID(file_filter.parts[0]) if file_filter else None
192193

193194
async with self.engine.connect() as conn:
194-
if project_id:
195-
project_access_rights = await get_project_access_rights(
196-
conn=conn, user_id=user_id, project_id=project_id
197-
)
198-
if not project_access_rights.read:
199-
raise ProjectAccessRightError(
200-
access_right="read", project_id=project_id
201-
)
202-
accessible_projects_ids = [project_id]
203-
else:
204-
accessible_projects_ids = await get_readable_project_ids(conn, user_id)
195+
accessible_projects_ids = await get_accessible_project_ids(
196+
conn, user_id=user_id, project_id=project_id
197+
)
205198

206199
# check if the file_filter is a directory or inside one
207200
dir_fmd = None
@@ -253,26 +246,64 @@ async def list_paths(
253246

254247
async def compute_path_total_size(self, user_id: UserID, *, path: Path) -> ByteSize:
255248
"""returns the total size of the path"""
249+
250+
# check access rights
256251
project_id = None
257-
node_id = None
258252
with contextlib.suppress(ValueError):
259253
# NOTE: we currently do not support anything else than project_id/node_id/file_path here, sorry chap
260254
project_id = ProjectID(path.parts[0])
261-
if len(path.parts) > 1:
262-
node_id = NodeID(path.parts[1])
263255
async with self.engine.connect() as conn:
264-
if project_id:
265-
project_access_rights = await get_project_access_rights(
266-
conn=conn, user_id=user_id, project_id=project_id
267-
)
268-
if not project_access_rights.read:
269-
raise ProjectAccessRightError(
270-
access_right="read", project_id=project_id
271-
)
272-
accessible_projects_ids = [project_id]
273-
else:
274-
accessible_projects_ids = await get_readable_project_ids(conn, user_id)
275-
return 0
256+
accessible_projects_ids = await get_accessible_project_ids(
257+
conn, user_id=user_id, project_id=project_id
258+
)
259+
260+
# compute the total size (files and base folders are in the DB)
261+
# use-cases:
262+
# 1. path is partial and smaller than in the DB --> all entries are in the DB (files and folder) sum will be done there
263+
# 2. path is partial and not in the DB --> entries are in S3, list the entries and sum their sizes
264+
# 3. path is complete and in the DB --> return directly from the DB
265+
# 4. path is complete and not in the DB --> entry in S3, returns directly from there
266+
267+
with contextlib.suppress(ValidationError):
268+
file_id = TypeAdapter(StorageFileID).validate_python(path)
269+
# path might be complete
270+
with contextlib.suppress(FileMetaDataNotFoundError):
271+
# file or folder is in DB
272+
async with self.engine.connect() as conn:
273+
fmd = await file_meta_data.get(conn, file_id=file_id)
274+
assert isinstance(fmd.file_size, ByteSize) # nosec
275+
return fmd.file_size
276+
# file or folder is in S3
277+
s3_metadata = await get_s3_client(self.app).get_directory_metadata(
278+
bucket=self.simcore_bucket_name, prefix=file_id
279+
)
280+
assert s3_metadata.size # nosec
281+
return s3_metadata.size
282+
283+
# path is partial not containing the minimal requirements to be a fully fledged file_id (only 1 or 2 parts), so everything is in DB
284+
async with self.engine.connect() as conn:
285+
fmds = await file_meta_data.list_filter_with_partial_file_id(
286+
conn,
287+
user_or_project_filter=UserOrProjectFilter(
288+
user_id=user_id, project_ids=accessible_projects_ids
289+
),
290+
file_id_prefix=f"{path}",
291+
partial_file_id=None,
292+
sha256_checksum=None,
293+
is_directory=None,
294+
)
295+
296+
# ensure file sizes are uptodate
297+
updated_fmds = []
298+
for metadata in fmds:
299+
if is_file_entry_valid(metadata):
300+
updated_fmds.append(metadata)
301+
continue
302+
updated_fmds.append(
303+
convert_db_to_model(await self._update_database_from_storage(metadata))
304+
)
305+
306+
return ByteSize(sum(fmd.file_size for fmd in updated_fmds))
276307

277308
async def list_files(
278309
self,
@@ -378,12 +409,6 @@ async def get_file(self, user_id: UserID, file_id: StorageFileID) -> FileMetaDat
378409
fmd = await self._update_database_from_storage(fmd)
379410
return convert_db_to_model(fmd)
380411

381-
async def can_read_file(self, user_id: UserID, file_id: StorageFileID):
382-
async with self.engine.connect() as conn:
383-
can = await get_file_access_rights(conn, int(user_id), file_id)
384-
if not can.read:
385-
raise FileAccessRightError(access_right="read", file_id=file_id)
386-
387412
async def create_file_upload_links(
388413
self,
389414
user_id: UserID,

services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@
1010
SimcoreS3FileID,
1111
StorageFileID,
1212
)
13+
from models_library.users import UserID
1314
from pydantic import ByteSize, NonNegativeInt, TypeAdapter
1415
from servicelib.utils import ensure_ends_with
1516
from sqlalchemy.ext.asyncio import AsyncConnection
1617

17-
from ..exceptions.errors import FileMetaDataNotFoundError
18+
from ..exceptions.errors import FileMetaDataNotFoundError, ProjectAccessRightError
1819
from ..models import FileMetaData, FileMetaDataAtDB, GenericCursor, PathMetaData
1920
from ..modules.db import file_meta_data
21+
from ..modules.db.access_layer import (
22+
get_project_access_rights,
23+
get_readable_project_ids,
24+
)
2025
from .utils import convert_db_to_model
2126

2227

@@ -207,3 +212,16 @@ async def list_child_paths_from_repository(
207212
)
208213

209214
return paths_metadata, next_cursor, total
215+
216+
217+
async def get_accessible_project_ids(
218+
conn: AsyncConnection, *, user_id: UserID, project_id: ProjectID | None
219+
) -> list[ProjectID]:
220+
if project_id:
221+
project_access_rights = await get_project_access_rights(
222+
conn=conn, user_id=user_id, project_id=project_id
223+
)
224+
if not project_access_rights.read:
225+
raise ProjectAccessRightError(access_right="read", project_id=project_id)
226+
return [project_id]
227+
return await get_readable_project_ids(conn, user_id)

services/storage/tests/unit/test_handlers_paths.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -525,16 +525,13 @@ async def test_path_compute_size(
525525
assert (
526526
len(project_params.allowed_file_sizes) == 1
527527
), "test preconditions are not filled! allowed file sizes should have only 1 option for this test"
528-
num_output_files_per_node = 3
529-
total_num_files_per_node = (
530-
num_output_files_per_node + project_params.workspace_files_count
531-
)
532-
expected_project_total_size = (
533-
project_params.allowed_file_sizes[0]
534-
* project_params.num_nodes
535-
* total_num_files_per_node
536-
)
537528
project, list_of_files = with_random_project_with_files
529+
530+
total_num_files = sum(
531+
len(files_in_node) for files_in_node in list_of_files.values()
532+
)
533+
expected_project_total_size = project_params.allowed_file_sizes[0] * total_num_files
534+
538535
url = url_from_operation_id(
539536
client,
540537
initialized_app,

0 commit comments

Comments
 (0)