manta-node 0.5b0.dev259__py3-none-any.whl → 0.5b0.dev264__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- manta_node/infrastructure/container/docker_adapter.py +93 -1
- manta_node/infrastructure/container/image_executor.py +52 -9
- manta_node/task_manager.py +1 -0
- {manta_node-0.5b0.dev259.dist-info → manta_node-0.5b0.dev264.dist-info}/METADATA +1 -1
- {manta_node-0.5b0.dev259.dist-info → manta_node-0.5b0.dev264.dist-info}/RECORD +9 -9
- {manta_node-0.5b0.dev259.dist-info → manta_node-0.5b0.dev264.dist-info}/WHEEL +0 -0
- {manta_node-0.5b0.dev259.dist-info → manta_node-0.5b0.dev264.dist-info}/entry_points.txt +0 -0
- {manta_node-0.5b0.dev259.dist-info → manta_node-0.5b0.dev264.dist-info}/licenses/LICENSE +0 -0
- {manta_node-0.5b0.dev259.dist-info → manta_node-0.5b0.dev264.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import concurrent.futures
|
|
1
3
|
from datetime import datetime
|
|
2
4
|
|
|
3
5
|
from aiodocker import Docker
|
|
@@ -13,6 +15,80 @@ class DockerRaiser:
|
|
|
13
15
|
tracer: Tracer
|
|
14
16
|
docker_client: DockerClient
|
|
15
17
|
|
|
18
|
+
# Dedicated, bounded executor for image pulls only. A multi-GB pull holds
|
|
19
|
+
# a thread for the full download; routing pulls through their own small
|
|
20
|
+
# pool (instead of asyncio.to_thread's shared default ThreadPoolExecutor,
|
|
21
|
+
# min(32, cpu+4) workers) keeps several concurrent large pulls from
|
|
22
|
+
# starving unrelated to_thread/run_in_executor work on the node. Process-
|
|
23
|
+
# lifetime singleton — the node process runs indefinitely, so no explicit
|
|
24
|
+
# shutdown is needed. image_needs_pull's inspect_distribution check is
|
|
25
|
+
# NOT routed through this pool: it's quick and issued right before the
|
|
26
|
+
# pull, and sharing a 4-slot pool with in-flight pulls could block it
|
|
27
|
+
# waiting for a pull slot.
|
|
28
|
+
_PULL_EXECUTOR = concurrent.futures.ThreadPoolExecutor(
|
|
29
|
+
max_workers=4, thread_name_prefix="docker-pull"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
async def image_needs_pull(self, image: str) -> bool:
|
|
33
|
+
"""
|
|
34
|
+
Check whether ``image`` is missing locally or stale relative to the registry.
|
|
35
|
+
|
|
36
|
+
Compares the local image's ``RepoDigests`` against the remote manifest
|
|
37
|
+
digest, obtained via the Docker daemon's own distribution API
|
|
38
|
+
(``docker_client.api.inspect_distribution``). This reuses the daemon's
|
|
39
|
+
registry auth, media-type negotiation and multi-arch resolution — the
|
|
40
|
+
same machinery ``docker pull`` itself relies on — instead of a bespoke
|
|
41
|
+
registry client.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
image : str
|
|
46
|
+
Image reference to check (e.g. ``"repo/image:latest"``)
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
bool
|
|
51
|
+
True if the image should be (re)pulled.
|
|
52
|
+
|
|
53
|
+
Notes
|
|
54
|
+
-----
|
|
55
|
+
Graceful degradation: if the registry check itself fails (unreachable,
|
|
56
|
+
unauthenticated, rate-limited, ...), a warning is logged and this falls
|
|
57
|
+
back to "pull only if absent locally" — it never raises out of task
|
|
58
|
+
launch.
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
local = await self.async_client.images.inspect(image)
|
|
62
|
+
except Exception:
|
|
63
|
+
# Image not present locally: must pull regardless of the registry.
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
local_digests = {
|
|
67
|
+
digest.split("@", 1)[1]
|
|
68
|
+
for digest in (local.get("RepoDigests") or [])
|
|
69
|
+
if "@" in digest
|
|
70
|
+
}
|
|
71
|
+
if not local_digests:
|
|
72
|
+
# Locally-built/tagged image (never pushed/pulled), or only
|
|
73
|
+
# malformed RepoDigests entries: nothing to compare against the
|
|
74
|
+
# registry. Treat as up-to-date rather than overwriting a
|
|
75
|
+
# local-only image on every launch.
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
distribution = await asyncio.to_thread(
|
|
80
|
+
self.docker_client.api.inspect_distribution, image
|
|
81
|
+
)
|
|
82
|
+
remote_digest = distribution["Descriptor"]["digest"]
|
|
83
|
+
except Exception as exc:
|
|
84
|
+
self.tracer.warning(
|
|
85
|
+
f"Cannot verify registry digest for {image}, skipping "
|
|
86
|
+
f"auto-pull staleness check: {exc!r}"
|
|
87
|
+
)
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
return remote_digest not in local_digests
|
|
91
|
+
|
|
16
92
|
async def run_or_raise(self, config: dict) -> DockerContainer:
|
|
17
93
|
"""
|
|
18
94
|
Start, pull or run a container
|
|
@@ -74,6 +150,19 @@ class DockerRaiser:
|
|
|
74
150
|
"""
|
|
75
151
|
Pull an image
|
|
76
152
|
|
|
153
|
+
Pulls via the sync docker-py client rather than the async aiodocker
|
|
154
|
+
client. docker-py auto-resolves registry credentials from
|
|
155
|
+
``~/.docker/config.json`` (including credHelpers/credsStore) — the
|
|
156
|
+
same client ``image_needs_pull`` already uses for
|
|
157
|
+
``inspect_distribution`` — whereas aiodocker pulls anonymously and
|
|
158
|
+
404s on private images with "pull access denied ... may require
|
|
159
|
+
'docker login'" (refs #348).
|
|
160
|
+
|
|
161
|
+
Routed through ``self._PULL_EXECUTOR`` (a dedicated, bounded thread
|
|
162
|
+
pool) rather than ``asyncio.to_thread``'s shared default executor, so
|
|
163
|
+
a long-running multi-GB pull can't starve unrelated to_thread/
|
|
164
|
+
run_in_executor work elsewhere on the node (refs #347 review).
|
|
165
|
+
|
|
77
166
|
Parameters
|
|
78
167
|
----------
|
|
79
168
|
image : str
|
|
@@ -85,7 +174,10 @@ class DockerRaiser:
|
|
|
85
174
|
Cannot pull image
|
|
86
175
|
"""
|
|
87
176
|
try:
|
|
88
|
-
|
|
177
|
+
loop = asyncio.get_running_loop()
|
|
178
|
+
await loop.run_in_executor(
|
|
179
|
+
self._PULL_EXECUTOR, self.docker_client.images.pull, image
|
|
180
|
+
)
|
|
89
181
|
except Exception as exc:
|
|
90
182
|
self.tracer.error(f"Image {image} cannot be pulled")
|
|
91
183
|
raise MantaDockerError(
|
|
@@ -115,37 +115,80 @@ class ImageExecutor(DockerRaiser):
|
|
|
115
115
|
task_id: ID,
|
|
116
116
|
image: str,
|
|
117
117
|
environment: dict | None = None,
|
|
118
|
+
gpu: bool = False,
|
|
119
|
+
force_pull: bool = False,
|
|
118
120
|
) -> datetime:
|
|
119
121
|
"""
|
|
120
|
-
Pull (
|
|
122
|
+
Pull (when missing or stale) and start a Docker image with its native ENTRYPOINT.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
swarm_id : ID
|
|
127
|
+
Swarm ID
|
|
128
|
+
task_id : ID
|
|
129
|
+
Task ID
|
|
130
|
+
image : str
|
|
131
|
+
Image to run
|
|
132
|
+
environment : Optional[dict]
|
|
133
|
+
Environment variables to inject into the container
|
|
134
|
+
gpu : bool
|
|
135
|
+
Attach GPU support, reusing the same ``gpu_config`` detection as
|
|
136
|
+
``ContainerManager.run_container``
|
|
137
|
+
force_pull : bool
|
|
138
|
+
Force a pull before running, bypassing the digest staleness check
|
|
121
139
|
|
|
122
140
|
Returns
|
|
123
141
|
-------
|
|
124
142
|
datetime
|
|
125
143
|
Container start time
|
|
144
|
+
|
|
145
|
+
Raises
|
|
146
|
+
------
|
|
147
|
+
MantaDockerError
|
|
148
|
+
GPU requested but not supported by this container manager
|
|
126
149
|
"""
|
|
127
150
|
tracked = set(getattr(self, "tasks", None) and self.tasks._env_ids or [])
|
|
128
151
|
tracked |= getattr(self, "_launching", None) or set()
|
|
129
152
|
await self._ensure_port_free(ORPHAN_PORT, tracked)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
153
|
+
|
|
154
|
+
# gpu_config is provided by ContainerManager via MRO in the real
|
|
155
|
+
# TaskManager(ContainerManager, ImageExecutor) composition; guard with
|
|
156
|
+
# getattr so a standalone ImageExecutor degrades to the same error
|
|
157
|
+
# run_container raises, instead of an AttributeError. This check runs
|
|
158
|
+
# BEFORE the pull decision — same ordering as run_container — so a
|
|
159
|
+
# GPU task on a non-GPU node fails fast instead of wastefully pulling
|
|
160
|
+
# a (possibly large) image first.
|
|
161
|
+
gpu_config = getattr(self, "gpu_config", None)
|
|
162
|
+
if gpu and not gpu_config:
|
|
163
|
+
raise MantaDockerError(
|
|
164
|
+
"GPU support is not enabled for this container manager.",
|
|
165
|
+
metadata=self.tracer.metadata,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if force_pull or await self.image_needs_pull(image):
|
|
169
|
+
self.tracer.info(f"Pulling image {image}...")
|
|
134
170
|
await self.pull_image_or_raise(image)
|
|
135
171
|
|
|
172
|
+
# Copy (not alias) gpu_config's HostConfig — it is shared, long-lived
|
|
173
|
+
# state on self; mutating it in place would leak NetworkMode/ExtraHosts
|
|
174
|
+
# onto every future GPU-enabled run.
|
|
175
|
+
host_config = (
|
|
176
|
+
dict(gpu_config.get("HostConfig", {})) if (gpu and gpu_config) else {}
|
|
177
|
+
)
|
|
178
|
+
host_config["NetworkMode"] = "host"
|
|
179
|
+
host_config["ExtraHosts"] = ["host.docker.internal:host-gateway"]
|
|
180
|
+
|
|
136
181
|
env = [f"{k}={v}" for k, v in environment.items()] if environment else None
|
|
137
182
|
config = {
|
|
138
183
|
"Image": image,
|
|
139
|
-
"HostConfig":
|
|
140
|
-
"NetworkMode": "host",
|
|
141
|
-
"ExtraHosts": ["host.docker.internal:host-gateway"],
|
|
142
|
-
},
|
|
184
|
+
"HostConfig": host_config,
|
|
143
185
|
"Env": env,
|
|
144
186
|
"Detach": True,
|
|
145
187
|
"LogConfig": {
|
|
146
188
|
"type": "json-file",
|
|
147
189
|
"config": {"max-size": "10m", "max-file": "3"},
|
|
148
190
|
},
|
|
191
|
+
"Runtime": (gpu_config.get("Runtime") if (gpu and gpu_config) else None),
|
|
149
192
|
"Labels": {
|
|
150
193
|
"com.docker.compose.project": "manta_tasks",
|
|
151
194
|
"manta.swarm-id": ID(swarm_id).xid,
|
manta_node/task_manager.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: manta_node
|
|
3
|
-
Version: 0.5b0.
|
|
3
|
+
Version: 0.5b0.dev264
|
|
4
4
|
Summary: Node side software of the Manta platform. It is responsible for the communication with the Manta Core and the execution of the tasks.
|
|
5
5
|
Author-email: Hugo Miralles <hugomiralles@manta-tech.io>, Matthew Thompson <matthew.thompson@manta-tech.io>
|
|
6
6
|
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
manta_node/__init__.py,sha256=bgGWXH3yQVhNf0v1ZGUBAy5Y51fqb0Y-EaLnocbcino,153
|
|
2
2
|
manta_node/__main__.py,sha256=SjrJyuMg_ZFKV6WOVe3MrtWM8Qmu065dkKZYl4n2YLw,1665
|
|
3
3
|
manta_node/node_orchestrator.py,sha256=DW1kiaeybytw656-OJqfhsRiJGMtGS0hfDzIpPyjmdU,17327
|
|
4
|
-
manta_node/task_manager.py,sha256=
|
|
4
|
+
manta_node/task_manager.py,sha256=aylQs3FAN-zaFd3G99eC_7XWDqBZyVXPJeW-ZqQ5euU,24402
|
|
5
5
|
manta_node/tasks.py,sha256=uBVaWwFq73vHxmoG8Fr4xxkX7rao37KU5hEs79jIrgo,6785
|
|
6
6
|
manta_node/utils.py,sha256=Hw4Iw2jYQYsfVLVJuBMt-_rMFBeml3nCDE2jiiXc8YM,1155
|
|
7
7
|
manta_node/cli/__init__.py,sha256=arzO3mXDJ-E2yeeytbZfN7mCswwxdQ43_QjyeL2E5yE,134
|
|
@@ -21,8 +21,8 @@ manta_node/infrastructure/__init__.py,sha256=yM60jKhdaD18BOsLb-0aR9D2XxGvrTnV92o
|
|
|
21
21
|
manta_node/infrastructure/config/__init__.py,sha256=VnKidPAVfzlZMb5WM-MggtsDLM8tdAFFWlB7WSiawzc,617
|
|
22
22
|
manta_node/infrastructure/config/node_config_manager.py,sha256=u9ga_z7EQ775wkgCVOT0oY4o2uYjAjhCzR1BZmmtNZ0,30605
|
|
23
23
|
manta_node/infrastructure/container/__init__.py,sha256=EO4SndUwmXrHczM4qkleUbxC7lTGz3c3Hd3iGrPg6D8,262
|
|
24
|
-
manta_node/infrastructure/container/docker_adapter.py,sha256=
|
|
25
|
-
manta_node/infrastructure/container/image_executor.py,sha256=
|
|
24
|
+
manta_node/infrastructure/container/docker_adapter.py,sha256=j4gMPB0FJI_jBRgHX-24er9AgHNU87kb6R4pyKhKaqs,12432
|
|
25
|
+
manta_node/infrastructure/container/image_executor.py,sha256=E3vT3PnfkVxnmM29_r74GKwL_Yr-iYp5PItAGHn-e24,10327
|
|
26
26
|
manta_node/infrastructure/container/manager.py,sha256=ZOMOZaeF0LZUEbcPWGIvpoAfmPmbtzXzayY_v7uP518,18198
|
|
27
27
|
manta_node/infrastructure/filesystem/__init__.py,sha256=nf6Fakp3wuFKjSAm_oihI6WE_yyUY6Uelr7A1GwSvXs,145
|
|
28
28
|
manta_node/infrastructure/filesystem/dataset_manager.py,sha256=8aexrjrkWZ7OCuS0HkjYXZFtg_8Y5WOJ540inK7BYRs,14968
|
|
@@ -35,9 +35,9 @@ manta_node/infrastructure/metrics/collector.py,sha256=9ESw__Pc2tV6ldG5yhos2IS7T5
|
|
|
35
35
|
manta_node/infrastructure/metrics/metrics_collector.py,sha256=WKN3e4q5PANHMrBScaxNSpSoS5ObGLH8QGR0gE1IAOk,10428
|
|
36
36
|
manta_node/infrastructure/mqtt/__init__.py,sha256=riXo1sXgO4YV6DHCmWRqK61BAmuGLdHiVdQFunAJadA,140
|
|
37
37
|
manta_node/infrastructure/mqtt/command_handler.py,sha256=Oj1MORxriUvkBymKsSuXoLqVaOoZWgAcoXy537x35oU,17389
|
|
38
|
-
manta_node-0.5b0.
|
|
39
|
-
manta_node-0.5b0.
|
|
40
|
-
manta_node-0.5b0.
|
|
41
|
-
manta_node-0.5b0.
|
|
42
|
-
manta_node-0.5b0.
|
|
43
|
-
manta_node-0.5b0.
|
|
38
|
+
manta_node-0.5b0.dev264.dist-info/licenses/LICENSE,sha256=P5HojupjKoAusNtHqWOvO-YFolwLFWQ7Uq0XVKK9nFQ,35291
|
|
39
|
+
manta_node-0.5b0.dev264.dist-info/METADATA,sha256=-QVhTQWavTpz2b-lDkW0eE56rGZ5qAZPR6dlCRJa5EA,44683
|
|
40
|
+
manta_node-0.5b0.dev264.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
41
|
+
manta_node-0.5b0.dev264.dist-info/entry_points.txt,sha256=C_zcw6mdmztNxVUZGLZ83UH5TwcSuqb2C-rB4e2CQoc,56
|
|
42
|
+
manta_node-0.5b0.dev264.dist-info/top_level.txt,sha256=nbT5J5WETuXM9UPoQckl29uUOq8wMYdRb9QDx5kAFeo,11
|
|
43
|
+
manta_node-0.5b0.dev264.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|