Skip to content

Commit c1c5d79

Browse files
authored
✨Simultaneous access: allow access to collaborative services (#8236)
1 parent c6999c2 commit c1c5d79

File tree

16 files changed

+96
-64
lines changed

16 files changed

+96
-64
lines changed

packages/models-library/src/models_library/api_schemas_directorv2/dynamic_services_service.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from functools import cached_property
22
from pathlib import Path
3+
from typing import Annotated
34

45
from pydantic import BaseModel, ConfigDict, Field
56
from pydantic.config import JsonDict
@@ -89,6 +90,11 @@ class RunningDynamicServiceDetails(ServiceDetails):
8990
alias="service_message",
9091
)
9192

93+
is_collaborative: Annotated[
94+
bool,
95+
Field(description="True if service allows collaboration (multi-tenant access)"),
96+
] = False
97+
9298
@staticmethod
9399
def _update_json_schema_extra(schema: JsonDict) -> None:
94100
schema.update(

packages/models-library/tests/test_docker.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ def test_docker_generic_tag(image_name: str, valid: bool):
112112
def test_simcore_service_docker_label_keys(obj_data: dict[str, Any]):
113113
simcore_service_docker_label_keys = SimcoreContainerLabels.model_validate(obj_data)
114114
exported_dict = simcore_service_docker_label_keys.to_simcore_runtime_docker_labels()
115-
assert all(
116-
isinstance(v, str) for v in exported_dict.values()
117-
), "docker labels must be strings!"
115+
assert all(isinstance(v, str) for v in exported_dict.values()), (
116+
"docker labels must be strings!"
117+
)
118118
assert all(
119119
key.startswith(_SIMCORE_RUNTIME_DOCKER_LABEL_PREFIX) for key in exported_dict
120120
)

services/autoscaling/tests/unit/conftest.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -756,9 +756,9 @@ async def _() -> None:
756756
assert tasks, f"no tasks available for {found_service['Spec']['Name']}"
757757
assert len(tasks) == 1
758758
service_task = tasks[0]
759-
assert (
760-
service_task["Status"]["State"] in expected_states
761-
), f"service {found_service['Spec']['Name']}'s task is {service_task['Status']['State']}"
759+
assert service_task["Status"]["State"] in expected_states, (
760+
f"service {found_service['Spec']['Name']}'s task is {service_task['Status']['State']}"
761+
)
762762
ctx.logger.info(
763763
"%s",
764764
f"service {found_service['Spec']['Name']} is now {service_task['Status']['State']} {'.' * number_of_success['count']}",
@@ -985,7 +985,9 @@ def _creator(
985985
assert (
986986
datetime.timedelta(seconds=10)
987987
< app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_TIME_BEFORE_TERMINATION
988-
), "this tests relies on the fact that the time before termination is above 10 seconds"
988+
), (
989+
"this tests relies on the fact that the time before termination is above 10 seconds"
990+
)
989991
assert app_settings.AUTOSCALING_EC2_INSTANCES
990992
seconds_delta = (
991993
-datetime.timedelta(seconds=10)
@@ -1126,12 +1128,12 @@ def ec2_instances_allowed_types_with_only_1_buffered(
11261128
allowed_ec2_types.items(),
11271129
)
11281130
)
1129-
assert (
1130-
allowed_ec2_types_with_buffer_defined
1131-
), "one type with buffer is needed for the tests!"
1132-
assert (
1133-
len(allowed_ec2_types_with_buffer_defined) == 1
1134-
), "more than one type with buffer is disallowed in this test!"
1131+
assert allowed_ec2_types_with_buffer_defined, (
1132+
"one type with buffer is needed for the tests!"
1133+
)
1134+
assert len(allowed_ec2_types_with_buffer_defined) == 1, (
1135+
"more than one type with buffer is disallowed in this test!"
1136+
)
11351137
return {
11361138
TypeAdapter(InstanceTypeType).validate_python(k): v
11371139
for k, v in allowed_ec2_types_with_buffer_defined.items()
@@ -1155,9 +1157,9 @@ def _by_buffer_count(
11551157
filter(_by_buffer_count, allowed_ec2_types.items())
11561158
)
11571159
assert allowed_ec2_types_with_buffer_defined, "you need one type with buffer"
1158-
assert (
1159-
len(allowed_ec2_types_with_buffer_defined) == 1
1160-
), "more than one type with buffer is disallowed in this test!"
1160+
assert len(allowed_ec2_types_with_buffer_defined) == 1, (
1161+
"more than one type with buffer is disallowed in this test!"
1162+
)
11611163
return next(iter(allowed_ec2_types_with_buffer_defined.values())).buffer_count
11621164

11631165

services/autoscaling/tests/unit/test_modules_cluster_scaling_dynamic.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -522,9 +522,9 @@ async def _test_cluster_scaling_up_and_down( # noqa: PLR0915
522522
all_instances = await ec2_client.describe_instances(Filters=instance_type_filters)
523523
assert not all_instances["Reservations"]
524524

525-
assert (
526-
scale_up_params.expected_num_instances == 1
527-
), "This test is not made to work with more than 1 expected instance. so please adapt if needed"
525+
assert scale_up_params.expected_num_instances == 1, (
526+
"This test is not made to work with more than 1 expected instance. so please adapt if needed"
527+
)
528528

529529
# create the service(s)
530530
created_docker_services = await create_services_batch(scale_up_params)
@@ -1283,7 +1283,9 @@ async def test_cluster_adapts_machines_on_the_fly( # noqa: PLR0915
12831283
assert (
12841284
scale_up_params1.num_services
12851285
>= app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_MAX_INSTANCES
1286-
), "this test requires to run a first batch of more services than the maximum number of instances allowed"
1286+
), (
1287+
"this test requires to run a first batch of more services than the maximum number of instances allowed"
1288+
)
12871289
# we have nothing running now
12881290
all_instances = await ec2_client.describe_instances()
12891291
assert not all_instances["Reservations"]
@@ -1500,7 +1502,9 @@ async def test_cluster_adapts_machines_on_the_fly( # noqa: PLR0915
15001502
assert "Instances" in reservation1
15011503
assert len(reservation1["Instances"]) == (
15021504
app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_MAX_INSTANCES
1503-
), f"expected {app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_MAX_INSTANCES} EC2 instances, found {len(reservation1['Instances'])}"
1505+
), (
1506+
f"expected {app_settings.AUTOSCALING_EC2_INSTANCES.EC2_INSTANCES_MAX_INSTANCES} EC2 instances, found {len(reservation1['Instances'])}"
1507+
)
15041508
for instance in reservation1["Instances"]:
15051509
assert "InstanceType" in instance
15061510
assert instance["InstanceType"] == scale_up_params1.expected_instance_type
@@ -1514,9 +1518,9 @@ async def test_cluster_adapts_machines_on_the_fly( # noqa: PLR0915
15141518

15151519
reservation2 = all_instances["Reservations"][1]
15161520
assert "Instances" in reservation2
1517-
assert (
1518-
len(reservation2["Instances"]) == 1
1519-
), f"expected 1 EC2 instances, found {len(reservation2['Instances'])}"
1521+
assert len(reservation2["Instances"]) == 1, (
1522+
f"expected 1 EC2 instances, found {len(reservation2['Instances'])}"
1523+
)
15201524
for instance in reservation2["Instances"]:
15211525
assert "InstanceType" in instance
15221526
assert instance["InstanceType"] == scale_up_params2.expected_instance_type
@@ -2243,9 +2247,9 @@ async def test_warm_buffers_only_replace_hot_buffer_if_service_is_started_issue7
22432247
# BUG REPRODUCTION
22442248
#
22452249
# start a service that imposes same type as the hot buffer
2246-
assert (
2247-
hot_buffer_instance_type == "t2.xlarge"
2248-
), "the test is hard-coded for this type and accordingly resource. If this changed then the resource shall be changed too"
2250+
assert hot_buffer_instance_type == "t2.xlarge", (
2251+
"the test is hard-coded for this type and accordingly resource. If this changed then the resource shall be changed too"
2252+
)
22492253
scale_up_params = _ScaleUpParams(
22502254
imposed_instance_type=hot_buffer_instance_type,
22512255
service_resources=Resources(

services/director-v2/openapi.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2878,6 +2878,12 @@
28782878
],
28792879
"title": "Service Message",
28802880
"description": "additional information related to service state"
2881+
},
2882+
"is_collaborative": {
2883+
"type": "boolean",
2884+
"title": "Is Collaborative",
2885+
"description": "True if service allows collaboration (multi-tenant access)",
2886+
"default": false
28812887
}
28822888
},
28832889
"type": "object",

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

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,7 @@ async def assemble_spec( # pylint: disable=too-many-arguments # noqa: PLR0913
296296
app.state.settings.DIRECTOR_V2_DOCKER_REGISTRY
297297
)
298298

299-
docker_compose_version = (
300-
app.state.settings.DYNAMIC_SERVICES.DYNAMIC_SCHEDULER.DYNAMIC_SIDECAR_DOCKER_COMPOSE_VERSION
301-
)
299+
docker_compose_version = app.state.settings.DYNAMIC_SERVICES.DYNAMIC_SCHEDULER.DYNAMIC_SIDECAR_DOCKER_COMPOSE_VERSION
302300

303301
egress_proxy_settings: EgressProxySettings = (
304302
app.state.settings.DYNAMIC_SERVICES.DYNAMIC_SIDECAR_EGRESS_PROXY_SETTINGS
@@ -348,19 +346,21 @@ async def assemble_spec( # pylint: disable=too-many-arguments # noqa: PLR0913
348346
service_version=service_version,
349347
product_name=product_name,
350348
)
351-
simcore_service_labels = await resolve_and_substitute_session_variables_in_model(
352-
app=app,
353-
model=simcore_service_labels,
354-
# NOTE: at this point all OsparcIdentifiers have to be replaced
355-
# an error will be raised otherwise
356-
safe=False,
357-
user_id=user_id,
358-
product_name=product_name,
359-
product_api_base_url=product_api_base_url,
360-
project_id=project_id,
361-
node_id=node_id,
362-
service_run_id=service_run_id,
363-
wallet_id=wallet_id,
349+
simcore_service_labels = (
350+
await resolve_and_substitute_session_variables_in_model(
351+
app=app,
352+
model=simcore_service_labels,
353+
# NOTE: at this point all OsparcIdentifiers have to be replaced
354+
# an error will be raised otherwise
355+
safe=False,
356+
user_id=user_id,
357+
product_name=product_name,
358+
product_api_base_url=product_api_base_url,
359+
project_id=project_id,
360+
node_id=node_id,
361+
service_run_id=service_run_id,
362+
wallet_id=wallet_id,
363+
)
364364
)
365365

366366
add_egress_configuration(

services/director-v2/src/simcore_service_director_v2/modules/dynamic_sidecar/docker_service_specs/proxy.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ def get_dynamic_proxy_spec(
3535
The proxy is used to create network isolation
3636
from the rest of the platform.
3737
"""
38-
assert (
39-
scheduler_data.product_name is not None
40-
), "ONLY for legacy. This function should not be called with product_name==None" # nosec
38+
assert scheduler_data.product_name is not None, (
39+
"ONLY for legacy. This function should not be called with product_name==None"
40+
) # nosec
4141

4242
proxy_settings: DynamicSidecarProxySettings = (
4343
dynamic_services_settings.DYNAMIC_SIDECAR_PROXY_SETTINGS

services/director-v2/src/simcore_service_director_v2/modules/dynamic_sidecar/docker_service_specs/sidecar.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -458,9 +458,9 @@ async def get_dynamic_sidecar_spec( # pylint:disable=too-many-arguments# noqa:
458458
dynamic_sidecar_settings=dynamic_sidecar_settings, app_settings=app_settings
459459
)
460460

461-
assert (
462-
scheduler_data.product_name is not None
463-
), "ONLY for legacy. This function should not be called with product_name==None" # nosec
461+
assert scheduler_data.product_name is not None, (
462+
"ONLY for legacy. This function should not be called with product_name==None"
463+
) # nosec
464464

465465
standard_simcore_docker_labels: dict[DockerLabelKey, str] = SimcoreContainerLabels(
466466
user_id=scheduler_data.user_id,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def create_model_from_scheduler_data(
8888
"service_port": scheduler_data.service_port,
8989
"service_state": service_state.value,
9090
"service_message": service_message,
91+
"is_collaborative": scheduler_data.is_collaborative,
9192
}
9293
)
9394

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def test_running_service_details_make_status(
153153
"service_host": scheduler_data.service_name,
154154
"user_id": scheduler_data.user_id,
155155
"service_port": scheduler_data.service_port,
156+
"is_collaborative": scheduler_data.is_collaborative,
156157
}
157158

158159
assert running_service_details_dict == expected_running_service_details

0 commit comments

Comments
 (0)