Skip to content
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ This document provides guidelines and best practices for using GitHub Copilot in
- ensure we use `sqlalchemy` >2 compatible code.
- ensure we use `pydantic` >2 compatible code.
- ensure we use `fastapi` >0.100 compatible code

- use f-string formatting

## Node.js-Specific Instructions

Expand Down
90 changes: 50 additions & 40 deletions packages/models-library/src/models_library/clusters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Literal, TypeAlias

from pydantic import AnyUrl, BaseModel, ConfigDict, Field, HttpUrl, field_validator
from pydantic.config import JsonDict
from pydantic.types import NonNegativeInt

from .groups import GroupID
Expand Down Expand Up @@ -36,18 +37,22 @@ class TLSAuthentication(_AuthenticationBase):
tls_client_cert: Path
tls_client_key: Path

model_config = ConfigDict(
json_schema_extra={
"examples": [
{
"type": "tls",
"tls_ca_file": "/path/to/ca_file",
"tls_client_cert": "/path/to/cert_file",
"tls_client_key": "/path/to/key_file",
},
]
}
)
@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
schema.update(
{
"examples": [
{
"type": "tls",
"tls_ca_file": "/path/to/ca_file",
"tls_client_cert": "/path/to/cert_file",
"tls_client_key": "/path/to/key_file",
},
]
}
)

model_config = ConfigDict(json_schema_extra=_update_json_schema_extra)


ClusterAuthentication: TypeAlias = NoAuthentication | TLSAuthentication
Expand All @@ -71,36 +76,41 @@ class BaseCluster(BaseModel):
create_enums_pre_validator(ClusterTypeInModel)
)

model_config = ConfigDict(
use_enum_values=True,
json_schema_extra={
"examples": [
{
"name": "My awesome cluster",
"type": ClusterTypeInModel.ON_PREMISE,
"owner": 12,
"endpoint": "https://registry.osparc-development.fake.dev",
"authentication": {
"type": "tls",
"tls_ca_file": "/path/to/ca_file",
"tls_client_cert": "/path/to/cert_file",
"tls_client_key": "/path/to/key_file",
@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
schema.update(
{
"examples": [
{
"name": "My awesome cluster",
"type": ClusterTypeInModel.ON_PREMISE,
"owner": 12,
"endpoint": "https://registry.osparc-development.fake.dev",
"authentication": {
"type": "tls",
"tls_ca_file": "/path/to/ca_file",
"tls_client_cert": "/path/to/cert_file",
"tls_client_key": "/path/to/key_file",
},
},
},
{
"name": "My AWS cluster",
"type": ClusterTypeInModel.AWS,
"owner": 154,
"endpoint": "https://registry.osparc-development.fake.dev",
"authentication": {
"type": "tls",
"tls_ca_file": "/path/to/ca_file",
"tls_client_cert": "/path/to/cert_file",
"tls_client_key": "/path/to/key_file",
{
"name": "My AWS cluster",
"type": ClusterTypeInModel.AWS,
"owner": 154,
"endpoint": "https://registry.osparc-development.fake.dev",
"authentication": {
"type": "tls",
"tls_ca_file": "/path/to/ca_file",
"tls_client_cert": "/path/to/cert_file",
"tls_client_key": "/path/to/key_file",
},
},
},
]
},
]
}
)

model_config = ConfigDict(
use_enum_values=True, json_schema_extra=_update_json_schema_extra
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ def setenvs_from_dict(
if isinstance(value, bool):
v = "true" if value else "false"

if isinstance(value, int | float):
v = f"{value}"

assert isinstance(v, str), (
"caller MUST explicitly stringify values since some cannot be done automatically"
f"e.g. json-like values. Check {key=},{value=}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def mock_rabbitmq_connection(mocker: MockerFixture) -> MockType:
def mock_rabbitmq_rpc_client_class(mocker: MockerFixture) -> MockType:
mock_rpc_client_instance = mocker.AsyncMock()
mocker.patch.object(
servicelib.rabbitmq._client_rpc.RabbitMQRPCClient,
servicelib.rabbitmq._client_rpc.RabbitMQRPCClient, # noqa: SLF001
"create",
return_value=mock_rpc_client_instance,
)
Expand Down Expand Up @@ -87,7 +87,6 @@ async def my_app_rpc_server(app: FastAPI, state: State) -> AsyncIterator[State]:

# setup rpc-client using rabbitmq_rpc_client_context
async def my_app_rpc_client(app: FastAPI, state: State) -> AsyncIterator[State]:

assert "RABBIT_CONNECTIVITY_LIFESPAN_NAME" in state

async with rabbitmq_rpc_client_context(
Expand Down Expand Up @@ -122,7 +121,6 @@ async def test_lifespan_rabbitmq_in_an_app(
startup_timeout=None if is_pdb_enabled else 10,
shutdown_timeout=None if is_pdb_enabled else 10,
):

# Verify that RabbitMQ responsiveness was checked
mock_rabbitmq_connection.assert_called_once_with(
app.state.settings.RABBITMQ.dsn
Expand Down
41 changes: 29 additions & 12 deletions packages/settings-library/src/settings_library/rabbit.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from functools import cached_property
from typing import ClassVar

from pydantic.config import JsonDict
from pydantic.networks import AnyUrl
from pydantic.types import SecretStr
from pydantic_settings import SettingsConfigDict
Expand All @@ -9,7 +11,7 @@


class RabbitDsn(AnyUrl):
allowed_schemes = {"amqp", "amqps"}
allowed_schemes: ClassVar[set[str]] = {"amqp", "amqps"}


class RabbitSettings(BaseCustomSettings):
Expand All @@ -35,16 +37,31 @@ def dsn(self) -> str:
)
return rabbit_dsn

@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
schema.update(
{
"examples": [
{
"RABBIT_HOST": "rabbitmq.example.com",
"RABBIT_USER": "guest",
"RABBIT_PASSWORD": "guest-password",
"RABBIT_SECURE": False,
"RABBIT_PORT": 5672,
},
{
"RABBIT_HOST": "secure.rabbitmq.example.com",
"RABBIT_USER": "guest",
"RABBIT_PASSWORD": "guest-password",
"RABBIT_SECURE": True,
"RABBIT_PORT": 15672,
},
]
}
)

model_config = SettingsConfigDict(
json_schema_extra={
"examples": [
# minimal required
{
"RABBIT_SECURE": "1",
"RABBIT_HOST": "localhost",
"RABBIT_USER": "user",
"RABBIT_PASSWORD": "foobar", # NOSONAR
}
],
}
extra="ignore",
populate_by_name=True,
json_schema_extra=_update_json_schema_extra,
)
6 changes: 4 additions & 2 deletions services/autoscaling/tests/unit/test_modules_dask.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
_authentication_types = [
NoAuthentication(),
TLSAuthentication.model_construct(
**TLSAuthentication.model_config["json_schema_extra"]["examples"][0]
**TLSAuthentication.model_json_schema()["examples"][0]
),
]

Expand Down Expand Up @@ -267,7 +267,9 @@ def _add_fct(x: int, y: int) -> int:
)
assert isinstance(exc, RuntimeError)
else:
result = await future_queued_task.result(timeout=_DASK_SCHEDULER_REACTION_TIME_S) # type: ignore
result = await future_queued_task.result(
timeout=_DASK_SCHEDULER_REACTION_TIME_S
) # type: ignore
assert result == 7

await _wait_for_dask_scheduler_to_change_state()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,14 @@ class PrimaryEC2InstancesSettings(BaseCustomSettings):
),
] = "172.20.0.0/14" # nosec

PRIMARY_EC2_INSTANCES_RABBIT: Annotated[
RabbitSettings | None,
Field(
description="defines the Rabbit settings for the primary instance (may be disabled)",
json_schema_extra={"auto_default_from_env": True},
),
]

@field_validator("PRIMARY_EC2_INSTANCES_ALLOWED_TYPES")
@classmethod
def _check_valid_instance_names(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ services:
AUTOSCALING_EC2_SECRET_ACCESS_KEY: ${CLUSTERS_KEEPER_EC2_SECRET_ACCESS_KEY}
AUTOSCALING_NODES_MONITORING: null
AUTOSCALING_POLL_INTERVAL: 00:00:10
AUTOSCALING_RABBITMQ: ${AUTOSCALING_RABBITMQ}
DASK_MONITORING_URL: tls://dask-scheduler:8786
DASK_SCHEDULER_AUTH: '{"type":"tls","tls_ca_file":"${DASK_TLS_CA_FILE}","tls_client_cert":"${DASK_TLS_CERT}","tls_client_key":"${DASK_TLS_KEY}"}'
EC2_INSTANCES_ALLOWED_TYPES: ${WORKERS_EC2_INSTANCES_ALLOWED_TYPES}
Expand Down Expand Up @@ -188,6 +189,7 @@ services:
networks:
cluster:


configs:
prometheus-config:
file: ./prometheus.yml
Expand All @@ -200,6 +202,7 @@ volumes:
redis-data:
prometheus-data:


secrets:
dask_tls_ca:
file: ${DASK_TLS_CA_FILE}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import yaml
from aws_library.ec2 import EC2InstanceBootSpecific, EC2InstanceData, EC2Tags
from aws_library.ec2._models import CommandStr
from common_library.json_serialization import json_dumps
from common_library.serialization import model_dump_with_secrets
from fastapi.encoders import jsonable_encoder
from models_library.api_schemas_clusters_keeper.clusters import (
ClusterState,
Expand Down Expand Up @@ -81,6 +83,8 @@ def _convert_to_env_list(entries: list[Any]) -> str:
def _convert_to_env_dict(entries: dict[str, Any]) -> str:
return f"'{json.dumps(jsonable_encoder(entries))}'"

assert app_settings.CLUSTERS_KEEPER_PRIMARY_EC2_INSTANCES # nosec

return [
f"CLUSTERS_KEEPER_EC2_ACCESS_KEY_ID={app_settings.CLUSTERS_KEEPER_EC2_ACCESS.EC2_ACCESS_KEY_ID}",
f"CLUSTERS_KEEPER_EC2_ENDPOINT={app_settings.CLUSTERS_KEEPER_EC2_ACCESS.EC2_ENDPOINT or 'null'}",
Expand All @@ -102,6 +106,7 @@ def _convert_to_env_dict(entries: dict[str, Any]) -> str:
f"WORKERS_EC2_INSTANCES_SUBNET_ID={app_settings.CLUSTERS_KEEPER_WORKERS_EC2_INSTANCES.WORKERS_EC2_INSTANCES_SUBNET_ID}",
f"WORKERS_EC2_INSTANCES_TIME_BEFORE_DRAINING={app_settings.CLUSTERS_KEEPER_WORKERS_EC2_INSTANCES.WORKERS_EC2_INSTANCES_TIME_BEFORE_DRAINING}",
f"WORKERS_EC2_INSTANCES_TIME_BEFORE_TERMINATION={app_settings.CLUSTERS_KEEPER_WORKERS_EC2_INSTANCES.WORKERS_EC2_INSTANCES_TIME_BEFORE_TERMINATION}",
f"AUTOSCALING_RABBITMQ={json_dumps(model_dump_with_secrets(app_settings.CLUSTERS_KEEPER_PRIMARY_EC2_INSTANCES.PRIMARY_EC2_INSTANCES_RABBIT, show_secrets=True)) if app_settings.CLUSTERS_KEEPER_PRIMARY_EC2_INSTANCES.PRIMARY_EC2_INSTANCES_RABBIT else 'null'}",
]


Expand Down
3 changes: 2 additions & 1 deletion services/clusters-keeper/tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ def disable_clusters_management_background_task(
@pytest.fixture
def disabled_rabbitmq(app_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("CLUSTERS_KEEPER_RABBITMQ", "null")
monkeypatch.delenv("RABBIT_HOST", raising=False)


@pytest.fixture
Expand Down Expand Up @@ -331,7 +332,7 @@ async def _do(num: int) -> list[str]:
"Tags": [
{
"Key": "Name",
"Value": f"{get_cluster_name(app_settings,user_id=user_id,wallet_id=wallet_id,is_manager=False)}_blahblah",
"Value": f"{get_cluster_name(app_settings, user_id=user_id, wallet_id=wallet_id, is_manager=False)}_blahblah",
}
],
}
Expand Down
2 changes: 1 addition & 1 deletion services/clusters-keeper/tests/unit/test_modules_dask.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
_authentication_types = [
NoAuthentication(),
TLSAuthentication.model_construct(
**TLSAuthentication.model_config["json_schema_extra"]["examples"][0]
**TLSAuthentication.model_json_schema()["examples"][0]
),
]

Expand Down
Loading
Loading