Skip to content

Commit 19d727b

Browse files
committed
moving copy to RPC
1 parent ec24a11 commit 19d727b

File tree

2 files changed

+307
-333
lines changed

2 files changed

+307
-333
lines changed

services/storage/tests/unit/test_handlers_simcore_s3.py

Lines changed: 2 additions & 331 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,30 @@
55
# pylint:disable=no-name-in-module
66
# pylint:disable=too-many-nested-blocks
77

8-
import asyncio
9-
import logging
108
import sys
119
from collections.abc import Awaitable, Callable
1210
from pathlib import Path
13-
from typing import Any, Literal
11+
from typing import Literal
1412

1513
import httpx
1614
import pytest
1715
from faker import Faker
1816
from fastapi import FastAPI
1917
from models_library.api_schemas_storage.storage_schemas import (
2018
FileMetaDataGet,
21-
FoldersBody,
2219
)
2320
from models_library.basic_types import SHA256Str
2421
from models_library.projects import ProjectID
25-
from models_library.projects_nodes_io import NodeID, NodeIDStr, SimcoreS3FileID
22+
from models_library.projects_nodes_io import SimcoreS3FileID
2623
from models_library.users import UserID
2724
from models_library.utils.fastapi_encoders import jsonable_encoder
2825
from pydantic import ByteSize, TypeAdapter
2926
from pytest_simcore.helpers.fastapi import url_from_operation_id
3027
from pytest_simcore.helpers.httpx_assert_checks import assert_status
31-
from pytest_simcore.helpers.logging_tools import log_context
32-
from pytest_simcore.helpers.storage_utils import (
33-
FileIDDict,
34-
ProjectWithFilesParams,
35-
get_updated_project,
36-
)
37-
from pytest_simcore.helpers.storage_utils_file_meta_data import (
38-
assert_file_meta_data_in_db,
39-
)
40-
from pytest_simcore.helpers.storage_utils_project import clone_project_data
4128
from servicelib.aiohttp import status
42-
from servicelib.fastapi.long_running_tasks.client import long_running_task_request
4329
from settings_library.s3 import S3Settings
4430
from simcore_service_storage.models import SearchFilesQueryParams
4531
from simcore_service_storage.simcore_s3_dsm import SimcoreS3DataManager
46-
from sqlalchemy.ext.asyncio import AsyncEngine
47-
from yarl import URL
4832

4933
pytest_simcore_core_services_selection = ["postgres", "rabbit"]
5034
pytest_simcore_ops_services_selection = ["adminer"]
@@ -53,28 +37,6 @@
5337
CURRENT_DIR = Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent
5438

5539

56-
@pytest.fixture
57-
def mock_datcore_download(mocker, client):
58-
# Use to mock downloading from DATCore
59-
async def _fake_download_to_file_or_raise(session, url, dest_path):
60-
with log_context(logging.INFO, f"Faking download: {url} -> {dest_path}"):
61-
Path(dest_path).write_text(
62-
"FAKE: test_create_and_delete_folders_from_project"
63-
)
64-
65-
mocker.patch(
66-
"simcore_service_storage.simcore_s3_dsm.download_to_file_or_raise",
67-
side_effect=_fake_download_to_file_or_raise,
68-
autospec=True,
69-
)
70-
71-
mocker.patch(
72-
"simcore_service_storage.simcore_s3_dsm.datcore_adapter.get_file_download_presigned_link",
73-
autospec=True,
74-
return_value=URL("https://httpbin.org/image"),
75-
)
76-
77-
7840
async def test_simcore_s3_access_returns_default(
7941
initialized_app: FastAPI, client: httpx.AsyncClient
8042
):
@@ -88,202 +50,6 @@ async def test_simcore_s3_access_returns_default(
8850
assert received_settings
8951

9052

91-
async def _request_copy_folders(
92-
initialized_app: FastAPI,
93-
client: httpx.AsyncClient,
94-
user_id: UserID,
95-
source_project: dict[str, Any],
96-
dst_project: dict[str, Any],
97-
nodes_map: dict[NodeID, NodeID],
98-
) -> dict[str, Any]:
99-
url = url_from_operation_id(
100-
client, initialized_app, "copy_folders_from_project"
101-
).with_query(user_id=user_id)
102-
103-
with log_context(
104-
logging.INFO,
105-
f"Copying folders from {source_project['uuid']} to {dst_project['uuid']}",
106-
) as ctx:
107-
async for lr_task in long_running_task_request(
108-
client,
109-
url,
110-
json=jsonable_encoder(
111-
FoldersBody(
112-
source=source_project, destination=dst_project, nodes_map=nodes_map
113-
)
114-
),
115-
):
116-
ctx.logger.info("%s", f"<-- current state is {lr_task.progress=}")
117-
if lr_task.done():
118-
return await lr_task.result()
119-
120-
pytest.fail(reason="Copy folders failed!")
121-
122-
123-
@pytest.mark.parametrize(
124-
"location_id",
125-
[SimcoreS3DataManager.get_location_id()],
126-
ids=[SimcoreS3DataManager.get_location_name()],
127-
indirect=True,
128-
)
129-
@pytest.mark.parametrize(
130-
"project_params",
131-
[
132-
ProjectWithFilesParams(
133-
num_nodes=12,
134-
allowed_file_sizes=(
135-
TypeAdapter(ByteSize).validate_python("7Mib"),
136-
TypeAdapter(ByteSize).validate_python("110Mib"),
137-
TypeAdapter(ByteSize).validate_python("1Mib"),
138-
),
139-
allowed_file_checksums=(
140-
TypeAdapter(SHA256Str).validate_python(
141-
"311e2e130d83cfea9c3b7560699c221b0b7f9e5d58b02870bd52b695d8b4aabd"
142-
),
143-
TypeAdapter(SHA256Str).validate_python(
144-
"08e297db979d3c84f6b072c2a1e269e8aa04e82714ca7b295933a0c9c0f62b2e"
145-
),
146-
TypeAdapter(SHA256Str).validate_python(
147-
"488f3b57932803bbf644593bd46d95599b1d4da1d63bc020d7ebe6f1c255f7f3"
148-
),
149-
),
150-
workspace_files_count=0,
151-
),
152-
],
153-
ids=str,
154-
)
155-
async def test_copy_folders_from_valid_project(
156-
short_dsm_cleaner_interval: int,
157-
initialized_app: FastAPI,
158-
client: httpx.AsyncClient,
159-
user_id: UserID,
160-
create_project: Callable[[], Awaitable[dict[str, Any]]],
161-
create_simcore_file_id: Callable[[ProjectID, NodeID, str], SimcoreS3FileID],
162-
sqlalchemy_async_engine: AsyncEngine,
163-
random_project_with_files: Callable[
164-
[ProjectWithFilesParams],
165-
Awaitable[
166-
tuple[dict[str, Any], dict[NodeID, dict[SimcoreS3FileID, FileIDDict]]]
167-
],
168-
],
169-
project_params: ProjectWithFilesParams,
170-
):
171-
# 1. create a src project with some files
172-
src_project, src_projects_list = await random_project_with_files(project_params)
173-
# 2. create a dst project without files
174-
dst_project, nodes_map = clone_project_data(src_project)
175-
dst_project = await create_project(**dst_project)
176-
# copy the project files
177-
data = await _request_copy_folders(
178-
initialized_app,
179-
client,
180-
user_id,
181-
src_project,
182-
dst_project,
183-
nodes_map={NodeID(i): NodeID(j) for i, j in nodes_map.items()},
184-
)
185-
assert data == jsonable_encoder(
186-
await get_updated_project(sqlalchemy_async_engine, dst_project["uuid"])
187-
)
188-
189-
# check that file meta data was effectively copied
190-
for src_node_id in src_projects_list:
191-
dst_node_id = nodes_map.get(
192-
TypeAdapter(NodeIDStr).validate_python(f"{src_node_id}")
193-
)
194-
assert dst_node_id
195-
for src_file_id, src_file in src_projects_list[src_node_id].items():
196-
path: Any = src_file["path"]
197-
assert isinstance(path, Path)
198-
checksum: Any = src_file["sha256_checksum"]
199-
assert isinstance(checksum, str)
200-
await assert_file_meta_data_in_db(
201-
sqlalchemy_async_engine,
202-
file_id=TypeAdapter(SimcoreS3FileID).validate_python(
203-
f"{src_file_id}".replace(
204-
f"{src_project['uuid']}", dst_project["uuid"]
205-
).replace(f"{src_node_id}", f"{dst_node_id}")
206-
),
207-
expected_entry_exists=True,
208-
expected_file_size=path.stat().st_size,
209-
expected_upload_id=None,
210-
expected_upload_expiration_date=None,
211-
expected_sha256_checksum=TypeAdapter(SHA256Str).validate_python(
212-
checksum
213-
),
214-
)
215-
216-
217-
async def _create_and_delete_folders_from_project(
218-
user_id: UserID,
219-
project: dict[str, Any],
220-
initialized_app: FastAPI,
221-
client: httpx.AsyncClient,
222-
project_db_creator: Callable,
223-
check_list_files: bool,
224-
) -> None:
225-
destination_project, nodes_map = clone_project_data(project)
226-
await project_db_creator(**destination_project)
227-
228-
# creating a copy
229-
data = await _request_copy_folders(
230-
initialized_app,
231-
client,
232-
user_id,
233-
project,
234-
destination_project,
235-
nodes_map={NodeID(i): NodeID(j) for i, j in nodes_map.items()},
236-
)
237-
238-
# data should be equal to the destination project, and all store entries should point to simcore.s3
239-
# NOTE: data is jsonized where destination project is not!
240-
assert jsonable_encoder(destination_project) == data
241-
242-
project_id = data["uuid"]
243-
244-
# list data to check all is here
245-
246-
if check_list_files:
247-
url = url_from_operation_id(
248-
client,
249-
initialized_app,
250-
"list_files_metadata",
251-
location_id=f"{SimcoreS3DataManager.get_location_id()}",
252-
).with_query(user_id=f"{user_id}", uuid_filter=f"{project_id}")
253-
254-
resp = await client.get(f"{url}")
255-
data, error = assert_status(resp, status.HTTP_200_OK, list[FileMetaDataGet])
256-
assert not error
257-
# DELETING
258-
url = url_from_operation_id(
259-
client,
260-
initialized_app,
261-
"delete_folders_of_project",
262-
folder_id=project_id,
263-
).with_query(user_id=f"{user_id}")
264-
resp = await client.delete(f"{url}")
265-
assert_status(resp, status.HTTP_204_NO_CONTENT, None)
266-
267-
# list data is gone
268-
if check_list_files:
269-
url = url_from_operation_id(
270-
client,
271-
initialized_app,
272-
"list_files_metadata",
273-
location_id=f"{SimcoreS3DataManager.get_location_id()}",
274-
).with_query(user_id=f"{user_id}", uuid_filter=f"{project_id}")
275-
resp = await client.get(f"{url}")
276-
data, error = assert_status(resp, status.HTTP_200_OK, list[FileMetaDataGet])
277-
assert not error
278-
assert not data
279-
280-
281-
@pytest.fixture
282-
def set_log_levels_for_noisy_libraries() -> None:
283-
# Reduce the log level for 'werkzeug'
284-
logging.getLogger("werkzeug").setLevel(logging.WARNING)
285-
286-
28753
async def test_connect_to_external(
28854
set_log_levels_for_noisy_libraries: None,
28955
initialized_app: FastAPI,
@@ -302,101 +68,6 @@ async def test_connect_to_external(
30268
print(data)
30369

30470

305-
@pytest.mark.parametrize(
306-
"location_id",
307-
[SimcoreS3DataManager.get_location_id()],
308-
ids=[SimcoreS3DataManager.get_location_name()],
309-
indirect=True,
310-
)
311-
@pytest.mark.parametrize(
312-
"project_params",
313-
[
314-
ProjectWithFilesParams(
315-
num_nodes=3,
316-
allowed_file_sizes=(
317-
TypeAdapter(ByteSize).validate_python("7Mib"),
318-
TypeAdapter(ByteSize).validate_python("110Mib"),
319-
TypeAdapter(ByteSize).validate_python("1Mib"),
320-
),
321-
workspace_files_count=0,
322-
)
323-
],
324-
)
325-
async def test_create_and_delete_folders_from_project(
326-
set_log_levels_for_noisy_libraries: None,
327-
initialized_app: FastAPI,
328-
client: httpx.AsyncClient,
329-
user_id: UserID,
330-
create_project: Callable[..., Awaitable[dict[str, Any]]],
331-
with_random_project_with_files: tuple[
332-
dict[str, Any],
333-
dict[NodeID, dict[SimcoreS3FileID, dict[str, Path | str]]],
334-
],
335-
mock_datcore_download,
336-
):
337-
project_in_db, _ = with_random_project_with_files
338-
await _create_and_delete_folders_from_project(
339-
user_id,
340-
project_in_db,
341-
initialized_app,
342-
client,
343-
create_project,
344-
check_list_files=True,
345-
)
346-
347-
348-
@pytest.mark.flaky(max_runs=3)
349-
@pytest.mark.parametrize(
350-
"location_id",
351-
[SimcoreS3DataManager.get_location_id()],
352-
ids=[SimcoreS3DataManager.get_location_name()],
353-
indirect=True,
354-
)
355-
@pytest.mark.parametrize(
356-
"project_params",
357-
[
358-
ProjectWithFilesParams(
359-
num_nodes=3,
360-
allowed_file_sizes=(
361-
TypeAdapter(ByteSize).validate_python("7Mib"),
362-
TypeAdapter(ByteSize).validate_python("110Mib"),
363-
TypeAdapter(ByteSize).validate_python("1Mib"),
364-
),
365-
workspace_files_count=0,
366-
)
367-
],
368-
)
369-
@pytest.mark.parametrize("num_concurrent_calls", [50])
370-
async def test_create_and_delete_folders_from_project_burst(
371-
set_log_levels_for_noisy_libraries: None,
372-
initialized_app: FastAPI,
373-
client: httpx.AsyncClient,
374-
user_id: UserID,
375-
with_random_project_with_files: tuple[
376-
dict[str, Any],
377-
dict[NodeID, dict[SimcoreS3FileID, dict[str, Path | str]]],
378-
],
379-
create_project: Callable[..., Awaitable[dict[str, Any]]],
380-
mock_datcore_download,
381-
num_concurrent_calls: int,
382-
):
383-
project_in_db, _ = with_random_project_with_files
384-
# NOTE: here the point is to NOT have a limit on the number of calls!!
385-
await asyncio.gather(
386-
*[
387-
_create_and_delete_folders_from_project(
388-
user_id,
389-
project_in_db,
390-
initialized_app,
391-
client,
392-
create_project,
393-
check_list_files=False,
394-
)
395-
for _ in range(num_concurrent_calls)
396-
]
397-
)
398-
399-
40071
@pytest.fixture
40172
async def uploaded_file_ids(
40273
faker: Faker,

0 commit comments

Comments
 (0)