Skip to content

Commit 2ffa877

Browse files
authored
♻️ Is638/dynamic sidecar refactors layout (round 4) (ITISFoundation#3156)
1 parent 3374169 commit 2ffa877

File tree

18 files changed

+315
-211
lines changed

18 files changed

+315
-211
lines changed

services/dynamic-sidecar/openapi.json

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,32 @@
7979
"summary": "Runs Docker Compose Up",
8080
"description": "Expects the docker-compose spec as raw-body utf-8 encoded text",
8181
"operationId": "runs_docker_compose_up_v1_containers_post",
82+
"parameters": [
83+
{
84+
"description": "docker-compose up command timeout run as a background",
85+
"required": false,
86+
"schema": {
87+
"title": "Command Timeout",
88+
"type": "number",
89+
"description": "docker-compose up command timeout run as a background",
90+
"default": 3600.0
91+
},
92+
"name": "command_timeout",
93+
"in": "query"
94+
},
95+
{
96+
"description": "docker-compose config timeout (EXPERIMENTAL)",
97+
"required": false,
98+
"schema": {
99+
"title": "Validation Timeout",
100+
"type": "number",
101+
"description": "docker-compose config timeout (EXPERIMENTAL)",
102+
"default": 60.0
103+
},
104+
"name": "validation_timeout",
105+
"in": "query"
106+
}
107+
],
82108
"responses": {
83109
"202": {
84110
"description": "Successful Response",
@@ -104,12 +130,12 @@
104130
"operationId": "runs_docker_compose_down_v1_containers_down_post",
105131
"parameters": [
106132
{
107-
"description": "docker-compose down command timeout default",
133+
"description": "docker-compose down command timeout default (EXPERIMENTAL)",
108134
"required": false,
109135
"schema": {
110136
"title": "Command Timeout",
111137
"type": "number",
112-
"description": "docker-compose down command timeout default",
138+
"description": "docker-compose down command timeout default (EXPERIMENTAL)",
113139
"default": 10.0
114140
},
115141
"name": "command_timeout",

services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/core/dependencies.py renamed to services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/api/_dependencies.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
""" Free functions to inject dependencies in routes handlers
2+
"""
3+
4+
15
from fastapi import Depends, FastAPI, Request
26
from fastapi.datastructures import State
37

4-
from ..models.domains.shared_store import SharedStore
8+
from ..core.rabbitmq import RabbitMQ
9+
from ..core.settings import DynamicSidecarSettings
510
from ..models.schemas.application_health import ApplicationHealth
11+
from ..models.shared_store import SharedStore
612
from ..modules.mounted_fs import MountedVolumes
7-
from .rabbitmq import RabbitMQ
8-
from .settings import DynamicSidecarSettings
913

1014

1115
def get_application(request: Request) -> FastAPI:

services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/api/containers.py

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import functools
44
import json
55
import logging
6-
import traceback
76
from typing import Any, Union
87

98
from fastapi import (
@@ -19,33 +18,29 @@
1918
from fastapi.responses import PlainTextResponse
2019
from servicelib.fastapi.requests_decorators import cancellable_request
2120

22-
from ..core.dependencies import (
23-
get_application,
24-
get_application_health,
25-
get_mounted_volumes,
26-
get_rabbitmq,
27-
get_settings,
28-
get_shared_store,
29-
)
21+
from ..core.docker_compose_utils import docker_compose_down, docker_compose_up
3022
from ..core.docker_logs import start_log_fetching, stop_log_fetching
3123
from ..core.docker_utils import docker_client
3224
from ..core.rabbitmq import RabbitMQ
3325
from ..core.settings import DynamicSidecarSettings
34-
from ..core.shared_handlers import (
35-
cleanup_containers_and_volumes,
36-
remove_the_compose_spec,
37-
write_file_and_run_command,
38-
)
3926
from ..core.utils import assemble_container_names
4027
from ..core.validation import (
4128
InvalidComposeSpec,
4229
parse_compose_spec,
4330
validate_compose_spec,
4431
)
45-
from ..models.domains.shared_store import SharedStore
4632
from ..models.schemas.application_health import ApplicationHealth
33+
from ..models.shared_store import SharedStore
4734
from ..modules.directory_watcher import directory_watcher_disabled
4835
from ..modules.mounted_fs import MountedVolumes
36+
from ._dependencies import (
37+
get_application,
38+
get_application_health,
39+
get_mounted_volumes,
40+
get_rabbitmq,
41+
get_settings,
42+
get_shared_store,
43+
)
4944

5045
logger = logging.getLogger(__name__)
5146

@@ -55,34 +50,26 @@ async def send_message(rabbitmq: RabbitMQ, message: str) -> None:
5550
await rabbitmq.post_log_message(f"[sidecar] {message}")
5651

5752

58-
async def _task_docker_compose_up(
53+
async def _task_docker_compose_up_and_send_message(
5954
settings: DynamicSidecarSettings,
6055
shared_store: SharedStore,
6156
app: FastAPI,
6257
application_health: ApplicationHealth,
6358
rabbitmq: RabbitMQ,
59+
command_timeout: float,
6460
) -> None:
6561
# building is a security risk hence is disabled via "--no-build" parameter
6662
await send_message(rabbitmq, "starting service containers")
63+
assert shared_store.compose_spec # nosec
6764

6865
with directory_watcher_disabled(app):
69-
await cleanup_containers_and_volumes(shared_store, settings)
70-
71-
assert shared_store.compose_spec is not None # nosec
72-
73-
command = (
74-
"docker-compose --project-name {project} --file {file_path} "
75-
"up --no-build --detach"
76-
)
77-
finished_without_errors, stdout = await write_file_and_run_command(
78-
settings=settings,
79-
file_content=shared_store.compose_spec,
80-
command=command,
81-
command_timeout=None,
66+
r = await docker_compose_up(
67+
shared_store, settings, command_timeout=command_timeout
8268
)
83-
message = f"Finished {command} with output\n{stdout}"
8469

85-
if finished_without_errors:
70+
message = f"Finished docker-compose up with output\n{r.decoded_stdout}"
71+
72+
if r.success:
8673
await send_message(rabbitmq, "service containers started")
8774
logger.info(message)
8875
for container_name in shared_store.container_names:
@@ -130,6 +117,12 @@ async def runs_docker_compose_up(
130117
application_health: ApplicationHealth = Depends(get_application_health),
131118
rabbitmq: RabbitMQ = Depends(get_rabbitmq),
132119
mounted_volumes: MountedVolumes = Depends(get_mounted_volumes),
120+
command_timeout: float = Query(
121+
3600.0, description="docker-compose up command timeout run as a background"
122+
),
123+
validation_timeout: float = Query(
124+
60.0, description="docker-compose config timeout (EXPERIMENTAL)"
125+
),
133126
) -> Union[list[str], dict[str, Any]]:
134127
"""Expects the docker-compose spec as raw-body utf-8 encoded text"""
135128

@@ -141,24 +134,28 @@ async def runs_docker_compose_up(
141134
settings=settings,
142135
compose_file_content=body_as_text,
143136
mounted_volumes=mounted_volumes,
137+
docker_compose_config_timeout=validation_timeout,
144138
)
145139
shared_store.container_names = assemble_container_names(
146140
shared_store.compose_spec
147141
)
142+
143+
logger.debug("Validated compose-spec:\n%s", f"{shared_store.compose_spec}")
144+
148145
except InvalidComposeSpec as e:
149-
logger.warning("Error detected %s", traceback.format_exc())
150146
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"{e}") from e
151147

152148
# run docker-compose in a background queue and return early
153149
assert shared_store.compose_spec is not None # nosec
154150
background_tasks.add_task(
155151
functools.partial(
156-
_task_docker_compose_up,
152+
_task_docker_compose_up_and_send_message,
157153
settings=settings,
158154
shared_store=shared_store,
159155
app=app,
160156
application_health=application_health,
161157
rabbitmq=rabbitmq,
158+
command_timeout=command_timeout,
162159
)
163160
)
164161

@@ -177,7 +174,7 @@ async def runs_docker_compose_up(
177174
)
178175
async def runs_docker_compose_down(
179176
command_timeout: float = Query(
180-
10.0, description="docker-compose down command timeout default"
177+
10.0, description="docker-compose down command timeout default (EXPERIMENTAL)"
181178
),
182179
settings: DynamicSidecarSettings = Depends(get_settings),
183180
shared_store: SharedStore = Depends(get_shared_store),
@@ -187,14 +184,13 @@ async def runs_docker_compose_down(
187184
and returns the docker-compose output"""
188185
# TODO: convert into long running operation
189186

190-
stored_compose_content = shared_store.compose_spec
191-
if stored_compose_content is None:
187+
if shared_store.compose_spec is None:
192188
raise HTTPException(
193189
status.HTTP_404_NOT_FOUND,
194-
detail="No spec for docker-compose down was found",
190+
detail="No compose-specs were found",
195191
)
196192

197-
finished_without_errors, stdout = await remove_the_compose_spec(
193+
result = await docker_compose_down(
198194
shared_store=shared_store,
199195
settings=settings,
200196
command_timeout=command_timeout,
@@ -203,11 +199,16 @@ async def runs_docker_compose_down(
203199
for container_name in shared_store.container_names:
204200
await stop_log_fetching(app, container_name)
205201

206-
if not finished_without_errors:
207-
logger.warning("docker-compose down command finished with errors\n%s", stdout)
208-
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=stdout)
202+
if not result.success:
203+
logger.warning(
204+
"docker-compose down command finished with errors\n%s",
205+
result.decoded_stdout,
206+
)
207+
raise HTTPException(
208+
status.HTTP_422_UNPROCESSABLE_ENTITY, detail=result.decoded_stdout
209+
)
209210

210-
return stdout
211+
return result.decoded_stdout
211212

212213

213214
@containers_router.get(

services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/api/containers_extension.py

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,22 @@
2222
from servicelib.utils import logged_gather
2323
from simcore_sdk.node_ports_v2.port_utils import is_file_type
2424

25-
from ..core.dependencies import (
26-
get_application,
27-
get_mounted_volumes,
28-
get_rabbitmq,
29-
get_settings,
30-
get_shared_store,
31-
)
25+
from ..core.docker_compose_utils import docker_compose_restart
3226
from ..core.docker_logs import start_log_fetching, stop_log_fetching
3327
from ..core.docker_utils import docker_client
3428
from ..core.rabbitmq import RabbitMQ
3529
from ..core.settings import DynamicSidecarSettings
36-
from ..core.shared_handlers import write_file_and_run_command
37-
from ..models.domains.shared_store import SharedStore
38-
from ..models.schemas.ports import PortTypeName
30+
from ..models.shared_store import SharedStore
3931
from ..modules import directory_watcher, nodeports
4032
from ..modules.data_manager import pull_path_if_exists, upload_path_if_exists
4133
from ..modules.mounted_fs import MountedVolumes
34+
from ._dependencies import (
35+
get_application,
36+
get_mounted_volumes,
37+
get_rabbitmq,
38+
get_settings,
39+
get_shared_store,
40+
)
4241
from .containers import send_message
4342

4443
logger = logging.getLogger(__name__)
@@ -143,7 +142,9 @@ async def pull_input_ports(
143142

144143
await send_message(rabbitmq, f"Pulling inputs for {port_keys}")
145144
transferred_bytes = await nodeports.download_target_ports(
146-
PortTypeName.INPUTS, mounted_volumes.disk_inputs_path, port_keys=port_keys
145+
nodeports.PortTypeName.INPUTS,
146+
mounted_volumes.disk_inputs_path,
147+
port_keys=port_keys,
147148
)
148149
await send_message(rabbitmq, "Finished pulling inputs")
149150
return int(transferred_bytes)
@@ -201,7 +202,9 @@ async def pull_output_ports(
201202

202203
await send_message(rabbitmq, f"Pulling output for {port_keys}")
203204
transferred_bytes = await nodeports.download_target_ports(
204-
PortTypeName.OUTPUTS, mounted_volumes.disk_outputs_path, port_keys=port_keys
205+
nodeports.PortTypeName.OUTPUTS,
206+
mounted_volumes.disk_outputs_path,
207+
port_keys=port_keys,
205208
)
206209
await send_message(rabbitmq, "Finished pulling output")
207210
return int(transferred_bytes)
@@ -255,10 +258,10 @@ async def restarts_containers(
255258
rabbitmq: RabbitMQ = Depends(get_rabbitmq),
256259
) -> None:
257260
"""Removes the previously started service
258-
and returns the docker-compose output"""
261+
and returns the docker-compose output
262+
"""
259263

260-
stored_compose_content = shared_store.compose_spec
261-
if stored_compose_content is None:
264+
if shared_store.compose_spec is None:
262265
raise HTTPException(
263266
status.HTTP_404_NOT_FOUND,
264267
detail="No spec for docker-compose command was found",
@@ -267,21 +270,17 @@ async def restarts_containers(
267270
for container_name in shared_store.container_names:
268271
await stop_log_fetching(app, container_name)
269272

270-
command = (
271-
"docker-compose --project-name {project} --file {file_path} "
272-
"restart --timeout {stop_and_remove_timeout}"
273+
result = await docker_compose_restart(
274+
shared_store.compose_spec, settings, command_timeout=command_timeout
273275
)
274276

275-
finished_without_errors, stdout = await write_file_and_run_command(
276-
settings=settings,
277-
file_content=stored_compose_content,
278-
command=command,
279-
command_timeout=command_timeout,
280-
)
281-
if not finished_without_errors:
282-
error_message = (f"'{command}' finished with errors\n{stdout}",)
283-
logger.warning(error_message)
284-
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=stdout)
277+
if not result.success:
278+
logger.warning(
279+
"docker-compose restart finished with errors\n%s", result.decoded_stdout
280+
)
281+
raise HTTPException(
282+
status.HTTP_422_UNPROCESSABLE_ENTITY, detail=result.decoded_stdout
283+
)
285284

286285
for container_name in shared_store.container_names:
287286
await start_log_fetching(app, container_name)

services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/api/health.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from fastapi import APIRouter, Depends, HTTPException, status
22

3-
from ..core.dependencies import get_application_health
43
from ..models.schemas.application_health import ApplicationHealth
4+
from ._dependencies import get_application_health
55

66
health_router = APIRouter()
77

services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/core/application.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@
66

77
from .._meta import API_VTAG, __version__
88
from ..api import main_router
9-
from ..models.domains.shared_store import SharedStore
109
from ..models.schemas.application_health import ApplicationHealth
10+
from ..models.shared_store import SharedStore
1111
from ..modules.directory_watcher import setup_directory_watcher
1212
from ..modules.mounted_fs import MountedVolumes, setup_mounted_fs
13+
from .docker_compose_utils import docker_compose_down
1314
from .docker_logs import setup_background_log_fetcher
1415
from .error_handlers import http_error_handler, node_not_found_error_handler
1516
from .errors import BaseDynamicSidecarError
1617
from .rabbitmq import setup_rabbitmq
1718
from .remote_debug import setup as remote_debug_setup
1819
from .settings import DynamicSidecarSettings
19-
from .shared_handlers import remove_the_compose_spec
2020
from .utils import login_registry, volumes_fix_permissions
2121

2222
logger = logging.getLogger(__name__)
@@ -101,7 +101,7 @@ async def _on_startup() -> None:
101101

102102
async def _on_shutdown() -> None:
103103
logger.info("Going to remove spawned containers")
104-
result = await remove_the_compose_spec(
104+
result = await docker_compose_down(
105105
shared_store=app.state.shared_store,
106106
settings=app.state.settings,
107107
command_timeout=app.state.settings.DYNAMIC_SIDECAR_DOCKER_COMPOSE_DOWN_TIMEOUT,

0 commit comments

Comments
 (0)