Skip to content

Commit aa39182

Browse files
authored
♻️ maintenance on director-v2 and other minors (ITISFoundation#2642)
1 parent f2bf136 commit aa39182

File tree

16 files changed

+192
-92
lines changed

16 files changed

+192
-92
lines changed

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,19 @@ local-registry: .env ## creates a local docker registry and configure simcore to
467467
@echo CATALOG_BACKGROUND_TASK_REST_TIME=1 >> .env
468468
# local registry set in $(local_registry):5000
469469
# images currently in registry:
470-
curl --silent $(local_registry):5000/v2/_catalog | jq
470+
@sleep 3
471+
curl --silent $(LOCAL_REGISTRY_HOSTNAME):5000/v2/_catalog | jq '.repositories'
472+
473+
info-registry: ## info on local registry (if any)
474+
# ping API
475+
curl --silent $(LOCAL_REGISTRY_HOSTNAME):5000/v2
476+
# list all
477+
curl --silent $(LOCAL_REGISTRY_HOSTNAME):5000/v2/_catalog | jq
478+
# target detail info (if set)
479+
$(if $(target),\
480+
@echo Tags for $(target); \
481+
curl --silent $(LOCAL_REGISTRY_HOSTNAME):5000/v2/$(target)/tags/list | jq ,\
482+
@echo No target set)
471483

472484

473485
## INFO -------------------------------

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class Config(_BaseConfig):
104104
}
105105

106106

107-
ComposeSpecLabel = Optional[Dict[str, Any]]
107+
ComposeSpecLabel = Dict[str, Any]
108108

109109

110110
class DynamicSidecarServiceLabels(BaseModel):

packages/models-library/src/models_library/settings/docker_registry.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

services/director-v2/src/simcore_service_director_v2/api/routes/dynamic_services.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,14 @@ async def create_dynamic_service(
9797
),
9898
scheduler: DynamicSidecarsScheduler = Depends(get_scheduler),
9999
) -> Union[DynamicServiceOut, RedirectResponse]:
100+
100101
simcore_service_labels: SimcoreServiceLabels = (
101102
await director_v0_client.get_service_labels(
102103
service=ServiceKeyVersion(key=service.key, version=service.version)
103104
)
104105
)
106+
107+
# LEGACY (backwards compatibility)
105108
if not simcore_service_labels.needs_dynamic_sidecar:
106109
# forward to director-v0
107110
redirect_url_with_query = director_v0_client.client.base_url.copy_with(
@@ -118,6 +121,7 @@ async def create_dynamic_service(
118121
logger.debug("Redirecting %s", redirect_url_with_query)
119122
return RedirectResponse(str(redirect_url_with_query))
120123

124+
#
121125
if not await is_dynamic_service_running(
122126
service.node_uuid, dynamic_services_settings.DYNAMIC_SIDECAR
123127
):

services/director-v2/src/simcore_service_director_v2/core/settings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ class DynamicSidecarSettings(BaseCustomSettings):
159159

160160
DYNAMIC_SIDECAR_PROXY_SETTINGS: DynamicSidecarProxySettings
161161

162+
DYNAMIC_SIDECAR_DOCKER_COMPOSE_VERSION: str = Field(
163+
"3.8", description="docker-compose version used in the compose-specs"
164+
)
165+
162166
@validator("DYNAMIC_SIDECAR_IMAGE", pre=True)
163167
@classmethod
164168
def strip_leading_slashes(cls, v) -> str:

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
EntrypointContainerNotFoundError,
1616
)
1717

18+
# PC -> SAN improvements to discuss
19+
#
20+
# TODO: Use logger, not logging!
21+
# - compose error msgs instead of log functions
22+
# TODO: Single instance of httpx client for all requests?: https://www.python-httpx.org/advanced/#why-use-a-client
23+
# - see services/api-server/src/simcore_service_api_server/utils/client_base.py (-> move to servicelib/fastapi ?)
24+
# TODO: context to unify session's error handling and logging
25+
# TODO: client function names equal/very similar to server handlers
26+
#
27+
1828
logger = logging.getLogger(__name__)
1929

2030

@@ -132,7 +142,7 @@ async def start_service_creation(
132142
response = await client.post(url, data=compose_spec)
133143
if response.status_code != status.HTTP_202_ACCEPTED:
134144
message = (
135-
f"ERROR during service creation request: "
145+
"ERROR during service creation request: "
136146
f"status={response.status_code}, body={response.text}"
137147
)
138148
logging.warning(message)
@@ -176,7 +186,7 @@ async def service_save_state(self, dynamic_sidecar_endpoint: str) -> None:
176186
logging.warning(message)
177187
raise DynamicSchedulerException(message)
178188
except httpx.HTTPError as e:
179-
log_httpx_http_error(url, "PUT", traceback.format_exc())
189+
log_httpx_http_error(url, "POST", traceback.format_exc())
180190
raise e
181191

182192
async def service_restore_state(self, dynamic_sidecar_endpoint: str) -> None:
@@ -191,9 +201,9 @@ async def service_restore_state(self, dynamic_sidecar_endpoint: str) -> None:
191201
)
192202
logging.warning(message)
193203
raise DynamicSchedulerException(message)
194-
except httpx.HTTPError as e:
195-
log_httpx_http_error(url, "PUT", traceback.format_exc())
196-
raise e
204+
except httpx.HTTPError:
205+
log_httpx_http_error(url, "POST", traceback.format_exc())
206+
raise
197207

198208
async def service_pull_input_ports(
199209
self, dynamic_sidecar_endpoint: str, port_keys: Optional[List[str]] = None
@@ -212,7 +222,7 @@ async def service_pull_input_ports(
212222
raise DynamicSchedulerException(message)
213223
return int(response.text)
214224
except httpx.HTTPError as e:
215-
log_httpx_http_error(url, "PUT", traceback.format_exc())
225+
log_httpx_http_error(url, "POST", traceback.format_exc())
216226
raise e
217227

218228
async def service_push_output_ports(
@@ -231,7 +241,7 @@ async def service_push_output_ports(
231241
logging.warning(message)
232242
raise DynamicSchedulerException(message)
233243
except httpx.HTTPError as e:
234-
log_httpx_http_error(url, "PUT", traceback.format_exc())
244+
log_httpx_http_error(url, "POST", traceback.format_exc())
235245
raise e
236246

237247
async def get_entrypoint_container_name(

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

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22
from copy import deepcopy
3-
from typing import Any, Dict, List, Optional
3+
from typing import Any, Dict, List, Optional, Union
44

55
import yaml
66
from fastapi.applications import FastAPI
@@ -10,10 +10,6 @@
1010
from .docker_service_specs import MATCH_SERVICE_VERSION, MATCH_SIMCORE_REGISTRY
1111

1212
CONTAINER_NAME = "container"
13-
BASE_SERVICE_SPEC: Dict[str, Any] = {
14-
"version": "3.8",
15-
"services": {CONTAINER_NAME: {}},
16-
}
1713

1814

1915
def _inject_proxy_network_configuration(
@@ -41,32 +37,61 @@ def _inject_proxy_network_configuration(
4137
target_container_spec["networks"] = container_networks
4238

4339

40+
EnvKeyEqValueList = List[str]
41+
EnvVarsMap = Dict[str, Optional[str]]
42+
43+
44+
class _environment_section:
45+
"""the 'environment' field in a docker-compose can be either a dict (EnvVarsMap)
46+
or a list of "key=value" (EnvKeyEqValueList)
47+
48+
These helpers can resolve parsing and exporting between these formats
49+
50+
SEE https://docs.docker.com/compose/compose-file/compose-file-v3/#environment
51+
"""
52+
53+
@staticmethod
54+
def parse(environment: Union[EnvVarsMap, EnvKeyEqValueList]) -> EnvVarsMap:
55+
envs = {}
56+
if isinstance(environment, list):
57+
for key_eq_value in environment:
58+
assert isinstance(key_eq_value, str) # nosec
59+
key, value, *_ = key_eq_value.split("=", maxsplit=1) + [
60+
None,
61+
] # type: ignore
62+
envs[key] = value
63+
else:
64+
assert isinstance(environment, dict) # nosec
65+
envs = deepcopy(environment)
66+
return envs
67+
68+
@staticmethod
69+
def export_as_list(environment: EnvVarsMap) -> EnvKeyEqValueList:
70+
envs = []
71+
for key, value in environment.items():
72+
if value is None:
73+
envs.append(f"{key}")
74+
else:
75+
envs.append(f"{key}={value}")
76+
return envs
77+
78+
4479
def _inject_paths_mappings(
4580
service_spec: Dict[str, Any], path_mappings: PathMappingsLabel
4681
) -> None:
4782
for service_name in service_spec["services"]:
4883
service_content = service_spec["services"][service_name]
4984

50-
environment_vars: List[str] = service_content.get("environment", [])
51-
environment_vars.append(f"DY_SIDECAR_PATH_INPUTS={path_mappings.inputs_path}")
52-
environment_vars.append(f"DY_SIDECAR_PATH_OUTPUTS={path_mappings.outputs_path}")
53-
str_path_mappings = json.dumps([str(x) for x in path_mappings.state_paths])
54-
environment_vars.append(f"DY_SIDECAR_STATE_PATHS={str_path_mappings}")
55-
56-
service_content["environment"] = environment_vars
57-
58-
59-
def _assemble_from_service_key_and_tag(
60-
resolved_registry_url: str,
61-
service_key: str,
62-
service_tag: str,
63-
):
64-
service_spec = deepcopy(BASE_SERVICE_SPEC)
65-
service_spec["services"][CONTAINER_NAME] = {
66-
"image": f"{resolved_registry_url}/{service_key}:{service_tag}"
67-
}
85+
env_vars: EnvVarsMap = _environment_section.parse(
86+
service_content.get("environment", {})
87+
)
88+
env_vars["DY_SIDECAR_PATH_INPUTS"] = f"{path_mappings.inputs_path}"
89+
env_vars["DY_SIDECAR_PATH_OUTPUTS"] = f"{path_mappings.outputs_path}"
90+
env_vars[
91+
"DY_SIDECAR_STATE_PATHS"
92+
] = f"{json.dumps([f'{p}' for p in path_mappings.state_paths])}"
6893

69-
return service_spec
94+
service_content["environment"] = _environment_section.export_as_list(env_vars)
7095

7196

7297
def _replace_env_vars_in_compose_spec(
@@ -81,12 +106,12 @@ def _replace_env_vars_in_compose_spec(
81106
return stringified_service_spec
82107

83108

84-
async def assemble_spec(
109+
def assemble_spec(
85110
app: FastAPI,
86111
service_key: str,
87112
service_tag: str,
88113
paths_mapping: PathMappingsLabel,
89-
compose_spec: ComposeSpecLabel,
114+
compose_spec: Optional[ComposeSpecLabel],
90115
container_http_entry: Optional[str],
91116
dynamic_sidecar_network_name: str,
92117
) -> str:
@@ -99,18 +124,27 @@ async def assemble_spec(
99124
app.state.settings.DIRECTOR_V2_DOCKER_REGISTRY
100125
)
101126

102-
container_name = container_http_entry
103-
service_spec = compose_spec
127+
docker_compose_version = (
128+
app.state.settings.DYNAMIC_SERVICES.DYNAMIC_SIDECAR.DYNAMIC_SIDECAR_DOCKER_COMPOSE_VERSION
129+
)
104130

105131
# when no compose yaml file was provided
106-
if service_spec is None:
107-
service_spec = _assemble_from_service_key_and_tag(
108-
resolved_registry_url=docker_registry_settings.resolved_registry_url,
109-
service_key=service_key,
110-
service_tag=service_tag,
111-
)
132+
if compose_spec is None:
133+
service_spec: Dict[str, Any] = {
134+
# NOTE: latest version does NOT require
135+
"version": docker_compose_version,
136+
"services": {
137+
CONTAINER_NAME: {
138+
"image": f"{docker_registry_settings.resolved_registry_url}/{service_key}:{service_tag}"
139+
}
140+
},
141+
}
112142
container_name = CONTAINER_NAME
143+
else:
144+
service_spec = compose_spec
145+
container_name = container_http_entry
113146

147+
assert service_spec is not None # nosec
114148
assert container_name is not None # nosec
115149

116150
_inject_proxy_network_configuration(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
CPU_1_PERCENT = 10000000
1010

1111

12-
async def get_dynamic_proxy_spec(
12+
def get_dynamic_proxy_spec(
1313
scheduler_data: SchedulerData,
1414
dynamic_sidecar_settings: DynamicSidecarSettings,
1515
dynamic_sidecar_network_id: str,

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
import logging
33
from collections import deque
4-
from typing import Any, Deque, Dict, List, cast
4+
from typing import Any, Deque, Dict, List, Optional, cast
55

66
from models_library.service_settings_labels import (
77
ComposeSpecLabel,
@@ -173,7 +173,9 @@ async def _extract_osparc_involved_service_labels(
173173
docker_image_name_by_services: Dict[str, SimcoreServiceLabels] = {
174174
_assemble_key(service_key=service_key, service_tag=service_tag): service_labels
175175
}
176-
compose_spec: ComposeSpecLabel = cast(ComposeSpecLabel, service_labels.compose_spec)
176+
compose_spec: Optional[ComposeSpecLabel] = cast(
177+
ComposeSpecLabel, service_labels.compose_spec
178+
)
177179
if compose_spec is None:
178180
return docker_image_name_by_services
179181

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def _get_environment_variables(
5757
}
5858

5959

60-
async def get_dynamic_sidecar_spec(
60+
def get_dynamic_sidecar_spec(
6161
scheduler_data: SchedulerData,
6262
dynamic_sidecar_settings: DynamicSidecarSettings,
6363
dynamic_sidecar_network_id: str,

0 commit comments

Comments
 (0)