Skip to content

Commit 938c6ae

Browse files
author
Andrei Neagu
committed
Merge remote-tracking branch 'upstream/master' into pr-osparc-s3-zip-stream-worker-code
2 parents afa7858 + c9296d7 commit 938c6ae

File tree

189 files changed

+8050
-2724
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

189 files changed

+8050
-2724
lines changed

.github/workflows/ci-testing-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ jobs:
514514
unit-test-storage:
515515
needs: changes
516516
if: ${{ needs.changes.outputs.storage == 'true' || github.event_name == 'push' }}
517-
timeout-minutes: 18 # if this timeout gets too small, then split the tests
517+
timeout-minutes: 25 # if this timeout gets too small, then split the tests
518518
name: "[unit] storage"
519519
runs-on: ${{ matrix.os }}
520520
strategy:

api/specs/web-server/_auth.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,29 @@
1515
from models_library.rest_error import EnvelopedError, Log
1616
from pydantic import BaseModel, Field, confloat
1717
from simcore_service_webserver._meta import API_VTAG
18-
from simcore_service_webserver.login._2fa_handlers import Resend2faBody
19-
from simcore_service_webserver.login._auth_handlers import (
18+
from simcore_service_webserver.login._controller.rest.auth import (
2019
LoginBody,
2120
LoginNextPage,
2221
LoginTwoFactorAuthBody,
2322
LogoutBody,
2423
)
25-
from simcore_service_webserver.login.handlers_change import (
24+
from simcore_service_webserver.login._controller.rest.change import (
2625
ChangeEmailBody,
2726
ChangePasswordBody,
2827
ResetPasswordBody,
2928
)
30-
from simcore_service_webserver.login.handlers_confirmation import (
29+
from simcore_service_webserver.login._controller.rest.confirmation import (
3130
PhoneConfirmationBody,
3231
ResetPasswordConfirmation,
3332
)
34-
from simcore_service_webserver.login.handlers_registration import (
33+
from simcore_service_webserver.login._controller.rest.registration import (
3534
InvitationCheck,
3635
InvitationInfo,
3736
RegisterBody,
3837
RegisterPhoneBody,
3938
RegisterPhoneNextPage,
4039
)
40+
from simcore_service_webserver.login._controller.rest.twofa import Resend2faBody
4141

4242
router = APIRouter(prefix=f"/{API_VTAG}", tags=["auth"])
4343

packages/aws-library/src/aws_library/s3/_client.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,30 @@ async def create(
8585
cls, settings: S3Settings, s3_max_concurrency: int = _S3_MAX_CONCURRENCY_DEFAULT
8686
) -> "SimcoreS3API":
8787
session = aioboto3.Session()
88-
session_client = session.client( # type: ignore[call-overload]
89-
"s3",
90-
endpoint_url=f"{settings.S3_ENDPOINT}",
91-
aws_access_key_id=settings.S3_ACCESS_KEY,
92-
aws_secret_access_key=settings.S3_SECRET_KEY,
93-
region_name=settings.S3_REGION,
94-
config=Config(signature_version="s3v4"),
95-
)
96-
assert isinstance(session_client, ClientCreatorContext) # nosec
88+
session_client = None
9789
exit_stack = contextlib.AsyncExitStack()
98-
s3_client = cast(S3Client, await exit_stack.enter_async_context(session_client))
99-
# NOTE: this triggers a botocore.exception.ClientError in case the connection is not made to the S3 backend
100-
await s3_client.list_buckets()
90+
try:
91+
session_client = session.client( # type: ignore[call-overload]
92+
"s3",
93+
endpoint_url=f"{settings.S3_ENDPOINT}",
94+
aws_access_key_id=settings.S3_ACCESS_KEY,
95+
aws_secret_access_key=settings.S3_SECRET_KEY,
96+
region_name=settings.S3_REGION,
97+
config=Config(signature_version="s3v4"),
98+
)
99+
assert isinstance(session_client, ClientCreatorContext) # nosec
101100

102-
return cls(s3_client, session, exit_stack, s3_max_concurrency)
101+
s3_client = cast(
102+
S3Client, await exit_stack.enter_async_context(session_client)
103+
)
104+
# NOTE: this triggers a botocore.exception.ClientError in case the connection is not made to the S3 backend
105+
await s3_client.list_buckets()
106+
107+
return cls(s3_client, session, exit_stack, s3_max_concurrency)
108+
except Exception:
109+
await exit_stack.aclose()
110+
111+
raise
103112

104113
async def close(self) -> None:
105114
await self._exit_stack.aclose()

packages/models-library/src/models_library/api_schemas_catalog/services.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,20 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
323323
)
324324

325325

326-
PageRpcServicesGetV2: TypeAlias = PageRpc[
326+
PageRpcLatestServiceGet: TypeAlias = PageRpc[
327327
# WARNING: keep this definition in models_library and not in the RPC interface
328+
# otherwise the metaclass PageRpc[*] will create *different* classes in server/client side
329+
# and will fail to serialize/deserialize these parameters when transmitted/received
328330
LatestServiceGet
329331
]
330332

333+
PageRpcServiceRelease: TypeAlias = PageRpc[
334+
# WARNING: keep this definition in models_library and not in the RPC interface
335+
# otherwise the metaclass PageRpc[*] will create *different* classes in server/client side
336+
# and will fail to serialize/deserialize these parameters when transmitted/received
337+
ServiceRelease
338+
]
339+
331340
ServiceResourcesGet: TypeAlias = ServiceResourcesDict
332341

333342

@@ -365,3 +374,6 @@ class MyServiceGet(CatalogOutputSchema):
365374

366375
owner: GroupID | None
367376
my_access_rights: ServiceGroupAccessRightsV2
377+
378+
379+
__all__: tuple[str, ...] = ("ServiceRelease",)

packages/models-library/src/models_library/rest_pagination.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
PageOffsetInt: TypeAlias = NonNegativeInt
3030

31+
PageTotalCount: TypeAlias = NonNegativeInt
32+
3133
DEFAULT_NUMBER_OF_ITEMS_PER_PAGE: Final[PageLimitInt] = TypeAdapter(
3234
PageLimitInt
3335
).validate_python(20)
@@ -70,7 +72,7 @@ class PageQueryParameters(RequestParameters):
7072

7173
class PageMetaInfoLimitOffset(BaseModel):
7274
limit: PositiveInt = DEFAULT_NUMBER_OF_ITEMS_PER_PAGE
73-
total: NonNegativeInt
75+
total: PageTotalCount
7476
offset: NonNegativeInt = 0
7577
count: NonNegativeInt
7678

packages/pytest-simcore/src/pytest_simcore/docker_compose.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
# pylint: disable=unused-argument
33
# pylint: disable=unused-variable
44

5-
""" Fixtures to create docker-compose.yaml configuration files (as in Makefile)
5+
"""Fixtures to create docker-compose.yaml configuration files (as in Makefile)
66
7-
- Basically runs `docker compose config
8-
- Services in stack can be selected using 'core_services_selection', 'ops_services_selection' fixtures
7+
- Basically runs `docker compose config
8+
- Services in stack can be selected using 'core_services_selection', 'ops_services_selection' fixtures
99
1010
"""
1111

@@ -391,6 +391,10 @@ def _filter_services_and_dump(
391391
if "environment" in service:
392392
service["environment"] = _minio_fix(service["environment"])
393393

394+
if name == "postgres":
395+
# NOTE: # -c fsync=off is not recommended for production as this disable writing to disk https://pythonspeed.com/articles/faster-db-tests/
396+
service["command"] += ["-c", "fsync=off"]
397+
394398
# updates current docker-compose (also versioned ... do not change by hand)
395399
with docker_compose_path.open("wt") as fh:
396400
yaml.dump(content, fh, default_flow_style=False)
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# pylint: disable=not-context-manager
2+
# pylint: disable=protected-access
3+
# pylint: disable=redefined-outer-name
4+
# pylint: disable=unused-argument
5+
# pylint: disable=unused-variable
6+
7+
8+
from models_library.api_schemas_catalog.services import LatestServiceGet, ServiceGetV2
9+
from models_library.api_schemas_webserver.catalog import (
10+
CatalogServiceUpdate,
11+
)
12+
from models_library.products import ProductName
13+
from models_library.rest_pagination import PageOffsetInt
14+
from models_library.rpc_pagination import PageLimitInt, PageRpc
15+
from models_library.services_history import ServiceRelease
16+
from models_library.services_types import ServiceKey, ServiceVersion
17+
from models_library.users import UserID
18+
from pydantic import NonNegativeInt, TypeAdapter
19+
from servicelib.rabbitmq._client_rpc import RabbitMQRPCClient
20+
21+
22+
class CatalogRpcSideEffects:
23+
# pylint: disable=no-self-use
24+
async def list_services_paginated(
25+
self,
26+
rpc_client: RabbitMQRPCClient,
27+
*,
28+
product_name: ProductName,
29+
user_id: UserID,
30+
limit: PageLimitInt,
31+
offset: NonNegativeInt,
32+
):
33+
assert rpc_client
34+
assert product_name
35+
assert user_id
36+
37+
items = TypeAdapter(list[LatestServiceGet]).validate_python(
38+
LatestServiceGet.model_json_schema()["examples"],
39+
)
40+
total_count = len(items)
41+
42+
return PageRpc[LatestServiceGet].create(
43+
items[offset : offset + limit],
44+
total=total_count,
45+
limit=limit,
46+
offset=offset,
47+
)
48+
49+
async def get_service(
50+
self,
51+
rpc_client: RabbitMQRPCClient,
52+
*,
53+
product_name: ProductName,
54+
user_id: UserID,
55+
service_key: ServiceKey,
56+
service_version: ServiceVersion,
57+
):
58+
assert rpc_client
59+
assert product_name
60+
assert user_id
61+
62+
got = ServiceGetV2.model_validate(
63+
ServiceGetV2.model_json_schema()["examples"][0]
64+
)
65+
got.version = service_version
66+
got.key = service_key
67+
68+
return got
69+
70+
async def update_service(
71+
self,
72+
rpc_client: RabbitMQRPCClient,
73+
*,
74+
product_name: ProductName,
75+
user_id: UserID,
76+
service_key: ServiceKey,
77+
service_version: ServiceVersion,
78+
update: CatalogServiceUpdate,
79+
):
80+
assert rpc_client
81+
assert product_name
82+
assert user_id
83+
84+
got = ServiceGetV2.model_validate(
85+
ServiceGetV2.model_json_schema()["examples"][0]
86+
)
87+
got.version = service_version
88+
got.key = service_key
89+
return got.model_copy(update=update.model_dump(exclude_unset=True))
90+
91+
async def list_my_service_history_paginated(
92+
self,
93+
rpc_client: RabbitMQRPCClient,
94+
*,
95+
product_name: ProductName,
96+
user_id: UserID,
97+
service_key: ServiceKey,
98+
offset: PageOffsetInt,
99+
limit: PageLimitInt,
100+
) -> PageRpc[ServiceRelease]:
101+
102+
assert rpc_client
103+
assert product_name
104+
assert user_id
105+
assert service_key
106+
107+
items = TypeAdapter(list[ServiceRelease]).validate_python(
108+
ServiceRelease.model_json_schema()["examples"],
109+
)
110+
total_count = len(items)
111+
112+
return PageRpc[ServiceRelease].create(
113+
items[offset : offset + limit],
114+
total=total_count,
115+
limit=limit,
116+
offset=offset,
117+
)

packages/pytest-simcore/src/pytest_simcore/helpers/playwright.py

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from typing import Any, Final
1212

1313
from playwright._impl._sync_base import EventContextManager
14-
from playwright.sync_api import APIRequestContext, FrameLocator, Page, Request
14+
from playwright.sync_api import APIRequestContext, FrameLocator, Locator, Page, Request
1515
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
1616
from playwright.sync_api import WebSocket
1717
from pydantic import AnyUrl
@@ -137,9 +137,7 @@ def on_framereceived(payload: str | bytes) -> None:
137137
ctx.logger.debug("⬆️ Frame received: %s", payload)
138138

139139
def on_close(_: WebSocket) -> None:
140-
ctx.logger.warning(
141-
"⚠️ WebSocket closed. Attempting to reconnect..."
142-
)
140+
ctx.logger.warning("⚠️ WebSocket closed. Attempting to reconnect...")
143141
self._attempt_reconnect(ctx.logger)
144142

145143
def on_socketerror(error_msg: str) -> None:
@@ -320,9 +318,9 @@ def __call__(self, message: str) -> bool:
320318
new_progress
321319
!= self._current_progress[node_progress_event.progress_type]
322320
):
323-
self._current_progress[
324-
node_progress_event.progress_type
325-
] = new_progress
321+
self._current_progress[node_progress_event.progress_type] = (
322+
new_progress
323+
)
326324

327325
self.logger.info(
328326
"Current startup progress [expected number of node-progress-types=%d]: %s",
@@ -343,29 +341,30 @@ def __call__(self, message: str) -> bool:
343341
url = (
344342
f"https://{self.node_id}.services.{self.get_partial_product_url()}"
345343
)
346-
response = self.api_request_context.get(url, timeout=1000)
347-
level = logging.DEBUG
348-
if (response.status >= 400) and (response.status not in (502, 503)):
349-
level = logging.ERROR
350-
self.logger.log(
351-
level,
352-
"Querying service endpoint in case we missed some websocket messages. Url: %s Response: '%s' TIP: %s",
353-
url,
354-
f"{response.status}: {response.text()}",
355-
(
356-
"We are emulating the frontend; a 5XX response is acceptable if the service is not yet ready."
357-
),
358-
)
344+
with contextlib.suppress(PlaywrightTimeoutError):
345+
response = self.api_request_context.get(url, timeout=1000)
346+
level = logging.DEBUG
347+
if (response.status >= 400) and (response.status not in (502, 503)):
348+
level = logging.ERROR
349+
self.logger.log(
350+
level,
351+
"Querying service endpoint in case we missed some websocket messages. Url: %s Response: '%s' TIP: %s",
352+
url,
353+
f"{response.status}: {response.text()}",
354+
(
355+
"We are emulating the frontend; a 5XX response is acceptable if the service is not yet ready."
356+
),
357+
)
359358

360-
if response.status <= 400:
361-
# NOTE: If the response status is less than 400, it means that the backend is ready (There are some services that respond with a 3XX)
362-
if self.got_expected_node_progress_types():
363-
self.logger.warning(
364-
"⚠️ Progress bar didn't receive 100 percent but service is already running: %s. TIP: we missed some websocket messages! ⚠️", # https://github.com/ITISFoundation/osparc-simcore/issues/6449
365-
self.get_current_progress(),
366-
)
367-
return True
368-
self._last_poll_timestamp = datetime.now(UTC)
359+
if response.status <= 400:
360+
# NOTE: If the response status is less than 400, it means that the backend is ready (There are some services that respond with a 3XX)
361+
if self.got_expected_node_progress_types():
362+
self.logger.warning(
363+
"⚠️ Progress bar didn't receive 100 percent but service is already running: %s. TIP: we missed some websocket messages! ⚠️", # https://github.com/ITISFoundation/osparc-simcore/issues/6449
364+
self.get_current_progress(),
365+
)
366+
return True
367+
self._last_poll_timestamp = datetime.now(UTC)
369368

370369
return False
371370

@@ -508,3 +507,16 @@ def app_mode_trigger_next_app(page: Page) -> None:
508507
):
509508
# Move to next step (this auto starts the next service)
510509
page.get_by_test_id("AppMode_NextBtn").click()
510+
511+
512+
def wait_for_label_text(
513+
page: Page, locator: str, substring: str, timeout: int = 10000
514+
) -> Locator:
515+
page.locator(locator).wait_for(state="visible", timeout=timeout)
516+
517+
page.wait_for_function(
518+
f"() => document.querySelector('{locator}').innerText.includes('{substring}')",
519+
timeout=timeout,
520+
)
521+
522+
return page.locator(locator)

packages/pytest-simcore/src/pytest_simcore/helpers/webserver_login.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
from simcore_service_webserver.db.models import UserRole, UserStatus
1212
from simcore_service_webserver.groups.api import auto_add_user_to_product_group
1313
from simcore_service_webserver.login._constants import MSG_LOGGED_IN
14-
from simcore_service_webserver.login._registration import create_invitation_token
15-
from simcore_service_webserver.login.storage import AsyncpgStorage, get_plugin_storage
14+
from simcore_service_webserver.login._invitations_service import create_invitation_token
15+
from simcore_service_webserver.login._login_repository_legacy import (
16+
AsyncpgStorage,
17+
get_plugin_storage,
18+
)
1619
from simcore_service_webserver.products.products_service import list_products
1720
from simcore_service_webserver.security.api import clean_auth_policy_cache
1821
from yarl import URL

0 commit comments

Comments
 (0)