Skip to content

Commit 44f85bf

Browse files
authored
Merge pull request #6744 from sanderegg/update-from-master-XXX
⬆️PydanticV2: Update from master
2 parents 65665ec + 57058f9 commit 44f85bf

File tree

28 files changed

+627
-194
lines changed

28 files changed

+627
-194
lines changed

packages/models-library/src/models_library/api_schemas_resource_usage_tracker/service_runs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class ServiceRunGet(BaseModel):
2020
user_email: str
2121
project_id: ProjectID
2222
project_name: str
23+
project_tags: list[str]
2324
node_id: NodeID
2425
node_name: str
2526
root_parent_project_id: ProjectID

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ class BaseCluster(BaseModel):
152152

153153

154154
ClusterID: TypeAlias = NonNegativeInt
155-
DEFAULT_CLUSTER_ID: Final[NonNegativeInt] = 0
155+
DEFAULT_CLUSTER_ID: Final[ClusterID] = 0
156156

157157

158158
class Cluster(BaseCluster):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""enhance projects_tags for RUT
2+
3+
Revision ID: 8e1f83486be7
4+
Revises: 8bfe65a5e294
5+
Create Date: 2024-11-15 09:12:57.789183+00:00
6+
7+
"""
8+
import sqlalchemy as sa
9+
from alembic import op
10+
11+
# revision identifiers, used by Alembic.
12+
revision = "8e1f83486be7"
13+
down_revision = "8bfe65a5e294"
14+
branch_labels = None
15+
depends_on = None
16+
17+
18+
def upgrade():
19+
# ### commands auto generated by Alembic - please adjust! ###
20+
op.add_column(
21+
"projects_tags", sa.Column("project_uuid_for_rut", sa.String(), nullable=True)
22+
)
23+
24+
# Migrate
25+
op.execute(
26+
sa.DDL(
27+
"""
28+
UPDATE projects_tags
29+
SET project_uuid_for_rut = projects.uuid
30+
FROM projects
31+
WHERE projects_tags.project_id = projects.id;
32+
"""
33+
)
34+
)
35+
36+
op.alter_column(
37+
"projects_tags",
38+
"project_uuid_for_rut",
39+
existing_type=sa.String(),
40+
nullable=False,
41+
)
42+
op.alter_column(
43+
"projects_tags", "project_id", existing_type=sa.BIGINT(), nullable=True
44+
)
45+
op.drop_constraint(
46+
"study_tags_study_id_tag_id_key", "projects_tags", type_="unique"
47+
)
48+
op.create_unique_constraint(
49+
"project_tags_project_uuid_unique",
50+
"projects_tags",
51+
["project_uuid_for_rut", "tag_id"],
52+
)
53+
op.drop_constraint("study_tags_study_id_fkey", "projects_tags", type_="foreignkey")
54+
op.create_foreign_key(
55+
"project_tags_project_id_fkey",
56+
"projects_tags",
57+
"projects",
58+
["project_id"],
59+
["id"],
60+
onupdate="CASCADE",
61+
ondelete="SET NULL",
62+
)
63+
# ### end Alembic commands ###
64+
65+
66+
def downgrade():
67+
# ### commands auto generated by Alembic - please adjust! ###
68+
op.drop_constraint(
69+
"project_tags_project_id_fkey", "projects_tags", type_="foreignkey"
70+
)
71+
op.create_foreign_key(
72+
"study_tags_study_id_fkey",
73+
"projects_tags",
74+
"projects",
75+
["project_id"],
76+
["id"],
77+
onupdate="CASCADE",
78+
ondelete="CASCADE",
79+
)
80+
op.drop_constraint(
81+
"project_tags_project_uuid_unique", "projects_tags", type_="unique"
82+
)
83+
op.create_unique_constraint(
84+
"study_tags_study_id_tag_id_key", "projects_tags", ["project_id", "tag_id"]
85+
)
86+
op.alter_column(
87+
"projects_tags", "project_id", existing_type=sa.BIGINT(), nullable=False
88+
)
89+
op.drop_column("projects_tags", "project_uuid_for_rut")
90+
# ### end Alembic commands ###

packages/postgres-database/src/simcore_postgres_database/models/projects_tags.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,27 @@
1313
sa.Column(
1414
"project_id",
1515
sa.BigInteger,
16-
sa.ForeignKey(projects.c.id, onupdate="CASCADE", ondelete="CASCADE"),
17-
nullable=False,
18-
doc="NOTE that project.c.id != project.c.uuid",
16+
sa.ForeignKey(
17+
projects.c.id,
18+
onupdate="CASCADE",
19+
ondelete="SET NULL",
20+
name="project_tags_project_id_fkey",
21+
),
22+
nullable=True, # <-- NULL means that project was deleted
23+
doc="NOTE that project.c.id != project.c.uuid. If project is deleted, we do not delete project in this table, we just set this column to NULL. Why? Because the `project_uuid_for_rut` is still used by resource usage tracker",
1924
),
2025
sa.Column(
2126
"tag_id",
2227
sa.BigInteger,
2328
sa.ForeignKey(tags.c.id, onupdate="CASCADE", ondelete="CASCADE"),
2429
nullable=False,
2530
),
26-
sa.UniqueConstraint("project_id", "tag_id"),
31+
sa.Column(
32+
"project_uuid_for_rut",
33+
sa.String,
34+
nullable=False,
35+
),
36+
sa.UniqueConstraint(
37+
"project_uuid_for_rut", "tag_id", name="project_tags_project_uuid_unique"
38+
),
2739
)

packages/postgres-database/src/simcore_postgres_database/utils_tags_sql.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import functools
2+
from uuid import UUID
23

34
import sqlalchemy as sa
45
from simcore_postgres_database.models.groups import user_to_groups
@@ -60,7 +61,7 @@ def get_tag_stmt(
6061
# aggregation ensures MOST PERMISSIVE policy of access-rights
6162
sa.func.bool_or(tags_access_rights.c.read).label("read"),
6263
sa.func.bool_or(tags_access_rights.c.write).label("write"),
63-
sa.func.bool_or(tags_access_rights.c.delete).label("delete")
64+
sa.func.bool_or(tags_access_rights.c.delete).label("delete"),
6465
)
6566
.select_from(
6667
_join_user_to_given_tag(
@@ -80,7 +81,7 @@ def list_tags_stmt(*, user_id: int):
8081
# aggregation ensures MOST PERMISSIVE policy of access-rights
8182
sa.func.bool_or(tags_access_rights.c.read).label("read"),
8283
sa.func.bool_or(tags_access_rights.c.write).label("write"),
83-
sa.func.bool_or(tags_access_rights.c.delete).label("delete")
84+
sa.func.bool_or(tags_access_rights.c.delete).label("delete"),
8485
)
8586
.select_from(
8687
_join_user_to_tags(
@@ -104,7 +105,7 @@ def count_groups_with_given_access_rights_stmt(
104105
tag_id: int,
105106
read: bool | None,
106107
write: bool | None,
107-
delete: bool | None
108+
delete: bool | None,
108109
):
109110
"""
110111
How many groups (from this user_id) are given EXACTLY these access permissions
@@ -192,12 +193,15 @@ def get_tags_for_project_stmt(*, project_index: int):
192193
)
193194

194195

195-
def add_tag_to_project_stmt(*, project_index: int, tag_id: int):
196+
def add_tag_to_project_stmt(
197+
*, project_index: int, tag_id: int, project_uuid_for_rut: UUID
198+
):
196199
return (
197200
pg_insert(projects_tags)
198201
.values(
199202
project_id=project_index,
200203
tag_id=tag_id,
204+
project_uuid_for_rut=f"{project_uuid_for_rut}",
201205
)
202206
.on_conflict_do_nothing()
203207
)

packages/postgres-database/tests/test_utils_tags.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,7 @@ def _check(func_smt, **kwargs):
668668
user_id = 425 # 4
669669
tag_id = 4
670670
project_index = 1
671+
project_uuid = "106f8b4b-ffb6-459a-a27b-981c779e6d3f"
671672
service_key = "simcore/services/comp/isolve"
672673
service_version = "2.0.85"
673674

@@ -726,6 +727,7 @@ def _check(func_smt, **kwargs):
726727
add_tag_to_project_stmt,
727728
project_index=project_index,
728729
tag_id=tag_id,
730+
project_uuid_for_rut=project_uuid,
729731
)
730732

731733
_check(

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
from collections import defaultdict
66
from collections.abc import Generator, Iterator
77
from dataclasses import dataclass, field
8+
from datetime import UTC, datetime, timedelta
89
from enum import Enum, unique
910
from typing import Any, Final
1011

12+
import httpx
1113
from playwright.sync_api import FrameLocator, Page, Request
1214
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
1315
from playwright.sync_api import WebSocket
16+
from pydantic import AnyUrl
1417
from pytest_simcore.helpers.logging_tools import log_context
1518

1619
SECOND: Final[int] = 1000
@@ -196,9 +199,11 @@ def __call__(self, message: str) -> None:
196199
class SocketIONodeProgressCompleteWaiter:
197200
node_id: str
198201
logger: logging.Logger
202+
product_url: AnyUrl
199203
_current_progress: dict[NodeProgressType, float] = field(
200204
default_factory=defaultdict
201205
)
206+
_last_poll_timestamp: datetime = field(default_factory=lambda: datetime.now(tz=UTC))
202207

203208
def __call__(self, message: str) -> bool:
204209
# socket.io encodes messages like so
@@ -234,6 +239,27 @@ def __call__(self, message: str) -> bool:
234239
round(progress, 1) == 1.0
235240
for progress in self._current_progress.values()
236241
)
242+
243+
_current_timestamp = datetime.now(UTC)
244+
if _current_timestamp - self._last_poll_timestamp > timedelta(seconds=5):
245+
url = f"https://{self.node_id}.services.{self.get_partial_product_url()}"
246+
response = httpx.get(url, timeout=10)
247+
self.logger.info(
248+
"Querying the service endpoint from the E2E test. Url: %s Response: %s",
249+
url,
250+
response,
251+
)
252+
if response.status_code <= 401:
253+
# 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)
254+
# MD: for now I have included 401 - as this also means that backend is ready
255+
if self.got_expected_node_progress_types():
256+
self.logger.warning(
257+
"⚠️ Progress bar didn't receive 100 percent but service is already running: %s ⚠️", # https://github.com/ITISFoundation/osparc-simcore/issues/6449
258+
self.get_current_progress(),
259+
)
260+
return True
261+
self._last_poll_timestamp = datetime.now(UTC)
262+
237263
return False
238264

239265
def got_expected_node_progress_types(self):
@@ -245,6 +271,9 @@ def got_expected_node_progress_types(self):
245271
def get_current_progress(self):
246272
return self._current_progress.values()
247273

274+
def get_partial_product_url(self):
275+
return f"{self.product_url}".split("//")[1]
276+
248277

249278
def wait_for_pipeline_state(
250279
current_state: RunningState,
@@ -332,9 +361,12 @@ def expected_service_running(
332361
websocket: WebSocket,
333362
timeout: int,
334363
press_start_button: bool,
364+
product_url: AnyUrl,
335365
) -> Generator[ServiceRunning, None, None]:
336366
with log_context(logging.INFO, msg="Waiting for node to run") as ctx:
337-
waiter = SocketIONodeProgressCompleteWaiter(node_id=node_id, logger=ctx.logger)
367+
waiter = SocketIONodeProgressCompleteWaiter(
368+
node_id=node_id, logger=ctx.logger, product_url=product_url
369+
)
338370
service_running = ServiceRunning(iframe_locator=None)
339371

340372
try:
@@ -366,12 +398,15 @@ def wait_for_service_running(
366398
websocket: WebSocket,
367399
timeout: int,
368400
press_start_button: bool,
401+
product_url: AnyUrl,
369402
) -> FrameLocator:
370403
"""NOTE: if the service was already started this will not work as some of the required websocket events will not be emitted again
371404
In which case this will need further adjutment"""
372405

373406
with log_context(logging.INFO, msg="Waiting for node to run") as ctx:
374-
waiter = SocketIONodeProgressCompleteWaiter(node_id=node_id, logger=ctx.logger)
407+
waiter = SocketIONodeProgressCompleteWaiter(
408+
node_id=node_id, logger=ctx.logger, product_url=product_url
409+
)
375410
with websocket.expect_event("framereceived", waiter, timeout=timeout):
376411
if press_start_button:
377412
_trigger_service_start(page, node_id)

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import arrow
88
from playwright.sync_api import FrameLocator, Page, WebSocket, expect
9-
from pydantic import ByteSize, TypeAdapter
9+
from pydantic import AnyUrl, ByteSize, TypeAdapter # pylint: disable=no-name-in-module
1010

1111
from .logging_tools import log_context
1212
from .playwright import (
@@ -104,6 +104,7 @@ def wait_for_launched_s4l(
104104
*,
105105
autoscaled: bool,
106106
copy_workspace: bool,
107+
product_url: AnyUrl,
107108
) -> WaitForS4LDict:
108109
with log_context(logging.INFO, "launch S4L") as ctx:
109110
predicate = S4LWaitForWebsocket(logger=ctx.logger)
@@ -129,6 +130,7 @@ def wait_for_launched_s4l(
129130
)
130131
+ (_S4L_COPY_WORKSPACE_TIME if copy_workspace else 0),
131132
press_start_button=False,
133+
product_url=product_url,
132134
)
133135
s4l_websocket = ws_info.value
134136
ctx.logger.info("acquired S4L websocket!")

services/api-server/src/simcore_service_api_server/core/health_checker.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ async def teardown(self):
6969

7070
@property
7171
def healthy(self) -> bool:
72-
return self._health_check_failure_count <= self._allowed_health_check_failures
72+
return self._rabbit_client.healthy and (
73+
self._health_check_failure_count <= self._allowed_health_check_failures
74+
) # https://github.com/ITISFoundation/osparc-simcore/pull/6662
7375

7476
@property
7577
def health_check_failure_count(self) -> NonNegativeInt:
@@ -82,9 +84,6 @@ async def _background_task_method(self):
8284
while self._dummy_queue.qsize() > 0:
8385
_ = self._dummy_queue.get_nowait()
8486
try:
85-
if not self._rabbit_client.healthy:
86-
self._increment_health_check_failure_count()
87-
return
8887
await asyncio.wait_for(
8988
self._rabbit_client.publish(
9089
self._dummy_message.channel_name, self._dummy_message

services/director-v2/src/simcore_service_director_v2/modules/comp_scheduler/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ async def start_scheduler() -> None:
1616
with log_context(
1717
_logger, level=logging.INFO, msg="starting computational scheduler"
1818
):
19-
app.state.scheduler = scheduler = await _scheduler_factory.create_from_db(
20-
app
21-
)
22-
scheduler.recover_scheduling()
19+
app.state.scheduler = await _scheduler_factory.create_from_db(app)
2320

2421
return start_scheduler
2522

0 commit comments

Comments
 (0)