Skip to content

Commit 1da8dab

Browse files
authored
♻️ Labels for metrics scraping (#3881)
1 parent 3b883ba commit 1da8dab

File tree

11 files changed

+160
-48
lines changed

11 files changed

+160
-48
lines changed

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import re
22
from typing import Optional
33

4-
from pydantic import ConstrainedStr, constr
4+
from models_library.generated_models.docker_rest_api import Task
5+
from models_library.projects import ProjectID
6+
from models_library.projects_nodes import NodeID
7+
from models_library.users import UserID
8+
from pydantic import BaseModel, ConstrainedStr, Field, constr
59

610
from .basic_regex import (
711
DOCKER_GENERIC_TAG_KEY_RE,
@@ -23,3 +27,28 @@ class DockerLabelKey(ConstrainedStr):
2327
class DockerGenericTag(ConstrainedStr):
2428
# NOTE: https://docs.docker.com/engine/reference/commandline/tag/#description
2529
regex: Optional[re.Pattern[str]] = DOCKER_GENERIC_TAG_KEY_RE
30+
31+
32+
class SimcoreServiceDockerLabelKeys(BaseModel):
33+
# NOTE: in a next PR, this should be moved to packages models-library and used
34+
# all over, and aliases should use io.simcore.service.*
35+
# https://github.com/ITISFoundation/osparc-simcore/issues/3638
36+
37+
user_id: UserID = Field(..., alias="user_id")
38+
project_id: ProjectID = Field(..., alias="study_id")
39+
node_id: NodeID = Field(..., alias="uuid")
40+
41+
def to_docker_labels(self) -> dict[str, str]:
42+
"""returns a dictionary of strings as required by docker"""
43+
std_export = self.dict(by_alias=True)
44+
return {k: f"{v}" for k, v in std_export.items()}
45+
46+
@classmethod
47+
def from_docker_task(cls, docker_task: Task) -> "SimcoreServiceDockerLabelKeys":
48+
assert docker_task.Spec # nosec
49+
assert docker_task.Spec.ContainerSpec # nosec
50+
task_labels = docker_task.Spec.ContainerSpec.Labels or {}
51+
return cls.parse_obj(task_labels)
52+
53+
class Config:
54+
allow_population_by_field_name = True

packages/models-library/tests/test_docker.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
# pylint: disable=unused-argument
33
# pylint: disable=unused-variable
44

5+
56
import pytest
6-
from models_library.docker import DockerGenericTag, DockerLabelKey
7+
from faker import Faker
8+
from models_library.docker import (
9+
DockerGenericTag,
10+
DockerLabelKey,
11+
SimcoreServiceDockerLabelKeys,
12+
)
713
from pydantic import ValidationError, parse_obj_as
814

915

@@ -93,3 +99,20 @@ def test_docker_generic_tag(image_name: str, valid: bool):
9399
else:
94100
with pytest.raises(ValidationError):
95101
parse_obj_as(DockerGenericTag, image_name)
102+
103+
104+
@pytest.fixture
105+
def osparc_docker_label_keys(
106+
faker: Faker,
107+
) -> SimcoreServiceDockerLabelKeys:
108+
return SimcoreServiceDockerLabelKeys.parse_obj(
109+
dict(user_id=faker.pyint(), project_id=faker.uuid4(), node_id=faker.uuid4())
110+
)
111+
112+
113+
def test_osparc_docker_label_keys_to_docker_labels(
114+
osparc_docker_label_keys: SimcoreServiceDockerLabelKeys,
115+
):
116+
exported_dict = osparc_docker_label_keys.to_docker_labels()
117+
assert all(isinstance(v, str) for v in exported_dict.values())
118+
assert parse_obj_as(SimcoreServiceDockerLabelKeys, exported_dict)

services/autoscaling/src/simcore_service_autoscaling/core/settings.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ class EC2InstancesSettings(BaseCustomSettings):
7878
"disabled when set to 0. Uses 1st machine defined in EC2_INSTANCES_ALLOWED_TYPES",
7979
)
8080

81-
EC2_INSTANCES_MAX_START_TIME: datetime.timedelta = Field(default=datetime.timedelta(minutes=3), description="Usual time taken an EC2 instance with the given AMI takes to be in 'running' mode")
81+
EC2_INSTANCES_MAX_START_TIME: datetime.timedelta = Field(
82+
default=datetime.timedelta(minutes=3),
83+
description="Usual time taken an EC2 instance with the given AMI takes to be in 'running' mode",
84+
)
8285

8386
@validator("EC2_INSTANCES_TIME_BEFORE_TERMINATION")
8487
@classmethod

services/autoscaling/src/simcore_service_autoscaling/models.py

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,11 @@
11
import datetime
22
from dataclasses import dataclass, field
33

4-
from models_library.generated_models.docker_rest_api import Node, Task
5-
from models_library.projects import ProjectID
6-
from models_library.projects_nodes import NodeID
7-
from models_library.users import UserID
8-
from pydantic import BaseModel, ByteSize, Field, NonNegativeFloat, PositiveInt
4+
from models_library.generated_models.docker_rest_api import Node
5+
from pydantic import BaseModel, ByteSize, NonNegativeFloat, PositiveInt
96
from types_aiobotocore_ec2.literals import InstanceStateNameType, InstanceTypeType
107

118

12-
class SimcoreServiceDockerLabelKeys(BaseModel):
13-
# NOTE: in a next PR, this should be moved to packages models-library and used
14-
# all over, and aliases should use io.simcore.service.*
15-
# https://github.com/ITISFoundation/osparc-simcore/issues/3638
16-
17-
user_id: UserID = Field(..., alias="user_id")
18-
project_id: ProjectID = Field(..., alias="study_id")
19-
node_id: NodeID = Field(..., alias="uuid")
20-
21-
def to_docker_labels(self) -> dict[str, str]:
22-
"""returns a dictionary of strings as required by docker"""
23-
std_export = self.dict(by_alias=True)
24-
return {k: f"{v}" for k, v in std_export.items()}
25-
26-
@classmethod
27-
def from_docker_task(cls, docker_task: Task) -> "SimcoreServiceDockerLabelKeys":
28-
assert docker_task.Spec # nosec
29-
assert docker_task.Spec.ContainerSpec # nosec
30-
task_labels = docker_task.Spec.ContainerSpec.Labels or {}
31-
return cls.parse_obj(task_labels)
32-
33-
class Config:
34-
allow_population_by_field_name = True
35-
36-
379
class Resources(BaseModel):
3810
cpus: NonNegativeFloat
3911
ram: ByteSize

services/autoscaling/src/simcore_service_autoscaling/utils/rabbitmq.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44

55
from fastapi import FastAPI
6+
from models_library.docker import SimcoreServiceDockerLabelKeys
67
from models_library.generated_models.docker_rest_api import Task
78
from models_library.rabbitmq_messages import (
89
LoggerRabbitMessage,
@@ -13,7 +14,7 @@
1314
from servicelib.logging_utils import log_catch
1415

1516
from ..core.settings import ApplicationSettings
16-
from ..models import Cluster, SimcoreServiceDockerLabelKeys
17+
from ..models import Cluster
1718
from ..modules.docker import AutoscalingDocker, get_docker_client
1819
from ..modules.rabbitmq import post_message
1920
from . import utils_docker

services/autoscaling/tests/unit/conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from faker import Faker
3232
from fakeredis.aioredis import FakeRedis
3333
from fastapi import FastAPI
34-
from models_library.docker import DockerLabelKey
34+
from models_library.docker import DockerLabelKey, SimcoreServiceDockerLabelKeys
3535
from models_library.generated_models.docker_rest_api import (
3636
Availability,
3737
Node,
@@ -50,7 +50,6 @@
5050
from settings_library.rabbit import RabbitSettings
5151
from simcore_service_autoscaling.core.application import create_app
5252
from simcore_service_autoscaling.core.settings import ApplicationSettings, EC2Settings
53-
from simcore_service_autoscaling.models import SimcoreServiceDockerLabelKeys
5453
from simcore_service_autoscaling.modules.docker import AutoscalingDocker
5554
from simcore_service_autoscaling.modules.ec2 import AutoscalingEC2, EC2InstanceData
5655
from tenacity import retry

services/autoscaling/tests/unit/test_models.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77

88
import aiodocker
99
import pytest
10+
from models_library.docker import SimcoreServiceDockerLabelKeys
1011
from models_library.generated_models.docker_rest_api import Service, Task
1112
from pydantic import ByteSize, ValidationError, parse_obj_as
12-
from simcore_service_autoscaling.models import Resources, SimcoreServiceDockerLabelKeys
13+
from simcore_service_autoscaling.models import Resources
1314

1415

1516
@pytest.mark.parametrize(
@@ -109,14 +110,6 @@ async def test_get_simcore_service_docker_labels_from_task_with_missing_labels_r
109110
SimcoreServiceDockerLabelKeys.from_docker_task(service_tasks[0])
110111

111112

112-
def test_osparc_docker_label_keys_to_docker_labels(
113-
osparc_docker_label_keys: SimcoreServiceDockerLabelKeys,
114-
):
115-
exported_dict = osparc_docker_label_keys.to_docker_labels()
116-
assert all(isinstance(v, str) for v in exported_dict.values())
117-
assert parse_obj_as(SimcoreServiceDockerLabelKeys, exported_dict)
118-
119-
120113
async def test_get_simcore_service_docker_labels(
121114
async_docker_client: aiodocker.Docker,
122115
create_service: Callable[[dict[str, Any], dict[str, str], str], Awaitable[Service]],

services/autoscaling/tests/unit/test_utils_rabbitmq.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import aiodocker
1010
from faker import Faker
1111
from fastapi import FastAPI
12-
from models_library.docker import DockerLabelKey
12+
from models_library.docker import DockerLabelKey, SimcoreServiceDockerLabelKeys
1313
from models_library.generated_models.docker_rest_api import Service, Task
1414
from models_library.rabbitmq_messages import (
1515
LoggerRabbitMessage,
@@ -20,7 +20,6 @@
2020
from pytest_mock.plugin import MockerFixture
2121
from servicelib.rabbitmq import RabbitMQClient
2222
from settings_library.rabbit import RabbitSettings
23-
from simcore_service_autoscaling.models import SimcoreServiceDockerLabelKeys
2423
from simcore_service_autoscaling.utils.rabbitmq import (
2524
post_task_log_message,
2625
post_task_progress_message,

services/director-v2/src/simcore_service_director_v2/modules/dynamic_sidecar/docker_compose_specs.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
from typing import Optional, Union
44

55
from fastapi.applications import FastAPI
6+
from models_library.docker import SimcoreServiceDockerLabelKeys
7+
from models_library.projects import ProjectID
8+
from models_library.projects_nodes_io import NodeID
69
from models_library.service_settings_labels import (
710
ComposeSpecLabel,
811
PathMappingsLabel,
@@ -182,6 +185,25 @@ def _update_resource_limits_and_reservations(
182185
spec["environment"] = environment
183186

184187

188+
def _update_container_labels(
189+
service_spec: ComposeSpecLabel,
190+
user_id: UserID,
191+
project_id: ProjectID,
192+
node_id: NodeID,
193+
) -> None:
194+
for spec in service_spec["services"].values():
195+
labels: list[str] = spec.setdefault("labels", [])
196+
197+
label_keys = SimcoreServiceDockerLabelKeys(
198+
user_id=user_id, study_id=project_id, uuid=node_id
199+
)
200+
docker_labels = [f"{k}={v}" for k, v in label_keys.to_docker_labels().items()]
201+
202+
for docker_label in docker_labels:
203+
if docker_label not in labels:
204+
labels.append(docker_label)
205+
206+
185207
def assemble_spec(
186208
*,
187209
app: FastAPI,
@@ -197,6 +219,8 @@ def assemble_spec(
197219
allow_internet_access: bool,
198220
product_name: str,
199221
user_id: UserID,
222+
project_id: ProjectID,
223+
node_id: NodeID,
200224
) -> str:
201225
"""
202226
returns a docker-compose spec used by
@@ -255,8 +279,14 @@ def assemble_spec(
255279
egress_proxy_settings=egress_proxy_settings,
256280
)
257281

282+
_update_container_labels(
283+
service_spec=service_spec,
284+
user_id=user_id,
285+
project_id=project_id,
286+
node_id=node_id,
287+
)
288+
258289
# TODO: will be used in next PR
259-
assert user_id # nosec
260290
assert product_name # nosec
261291

262292
stringified_service_spec = replace_env_vars_in_compose_spec(

services/director-v2/src/simcore_service_director_v2/modules/dynamic_sidecar/scheduler/_core/_events.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,10 @@ async def action(cls, app: FastAPI, scheduler_data: SchedulerData) -> None:
480480
allow_internet_access=allow_internet_access,
481481
product_name=scheduler_data.product_name,
482482
user_id=scheduler_data.user_id,
483+
project_id=scheduler_data.project_id,
484+
node_id=scheduler_data.node_uuid,
483485
)
486+
484487
logger.debug(
485488
"Starting containers %s with compose-specs:\n%s",
486489
scheduler_data.service_name,

services/director-v2/tests/unit/test_modules_dynamic_sidecar_docker_compose_specs.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33

44

55
from typing import Any
6+
from uuid import uuid4
67

78
import pytest
89
import yaml
10+
from models_library.projects import ProjectID
11+
from models_library.projects_nodes_io import NodeID
912
from models_library.services_resources import (
1013
DEFAULT_SINGLE_SERVICE_NAME,
1114
ResourcesDict,
1215
ServiceResourcesDict,
1316
)
17+
from models_library.users import UserID
1418
from pydantic import parse_obj_as
1519
from servicelib.resources import CPU_RESOURCE_LIMIT_KEY, MEM_RESOURCE_LIMIT_KEY
1620
from simcore_service_director_v2.modules.dynamic_sidecar import docker_compose_specs
@@ -139,3 +143,59 @@ async def test_inject_resource_limits_and_reservations(
139143
in spec["environment"]
140144
)
141145
assert f"{MEM_RESOURCE_LIMIT_KEY}={memory.limit}" in spec["environment"]
146+
147+
148+
USER_ID: UserID = 1
149+
PROJECT_ID: ProjectID = uuid4()
150+
NODE_ID: NodeID = uuid4()
151+
152+
153+
@pytest.mark.parametrize(
154+
"service_spec, expected_result",
155+
[
156+
pytest.param(
157+
{"services": {"service-1": {}}},
158+
{
159+
"services": {
160+
"service-1": {
161+
"labels": [
162+
f"user_id={USER_ID}",
163+
f"study_id={PROJECT_ID}",
164+
f"uuid={NODE_ID}",
165+
]
166+
}
167+
}
168+
},
169+
id="single_service",
170+
),
171+
pytest.param(
172+
{"services": {"service-1": {}, "service-2": {}}},
173+
{
174+
"services": {
175+
"service-1": {
176+
"labels": [
177+
f"user_id={USER_ID}",
178+
f"study_id={PROJECT_ID}",
179+
f"uuid={NODE_ID}",
180+
]
181+
},
182+
"service-2": {
183+
"labels": [
184+
f"user_id={USER_ID}",
185+
f"study_id={PROJECT_ID}",
186+
f"uuid={NODE_ID}",
187+
]
188+
},
189+
}
190+
},
191+
id="multiple_services",
192+
),
193+
],
194+
)
195+
async def test_update_container_labels(
196+
service_spec: dict[str, Any], expected_result: dict[str, Any]
197+
):
198+
docker_compose_specs._update_container_labels(
199+
service_spec, USER_ID, PROJECT_ID, NODE_ID
200+
)
201+
assert service_spec == expected_result

0 commit comments

Comments
 (0)