manta-node 0.6b1.dev664__py3-none-any.whl → 0.6b5.dev672__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/cli/_version_generated.py +3 -3
- manta_node/cli/version.py +1 -1
- manta_node/infrastructure/container/docker_adapter.py +149 -4
- {manta_node-0.6b1.dev664.dist-info → manta_node-0.6b5.dev672.dist-info}/METADATA +3 -3
- {manta_node-0.6b1.dev664.dist-info → manta_node-0.6b5.dev672.dist-info}/RECORD +9 -9
- {manta_node-0.6b1.dev664.dist-info → manta_node-0.6b5.dev672.dist-info}/WHEEL +0 -0
- {manta_node-0.6b1.dev664.dist-info → manta_node-0.6b5.dev672.dist-info}/entry_points.txt +0 -0
- {manta_node-0.6b1.dev664.dist-info → manta_node-0.6b5.dev672.dist-info}/licenses/LICENSE +0 -0
- {manta_node-0.6b1.dev664.dist-info → manta_node-0.6b5.dev672.dist-info}/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ This file is auto-generated at build time by scripts/generate_version.py.
|
|
|
4
4
|
Do not edit manually.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "2026.09.18.
|
|
7
|
+
__version__ = "2026.09.18.ba835c28"
|
|
8
8
|
__build_date__ = "2026.09.18"
|
|
9
|
-
__commit_id__ = "
|
|
10
|
-
__package_version__ = "0.
|
|
9
|
+
__commit_id__ = "ba835c28"
|
|
10
|
+
__package_version__ = "0.6b5"
|
manta_node/cli/version.py
CHANGED
|
@@ -98,7 +98,7 @@ def get_full_version_info() -> dict:
|
|
|
98
98
|
"version": get_version_string(),
|
|
99
99
|
"date": datetime.now().strftime("%Y.%m.%d"),
|
|
100
100
|
"commit": commit_id or "unknown",
|
|
101
|
-
"package_version": "0.
|
|
101
|
+
"package_version": "0.6b5", # mirrors pyproject.toml [project].version; nothing syncs the two, so both move together
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import concurrent.futures
|
|
3
|
+
import shutil
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from enum import Enum
|
|
5
6
|
|
|
@@ -7,6 +8,7 @@ from aiodocker import Docker
|
|
|
7
8
|
from aiodocker.docker import DockerContainer
|
|
8
9
|
from aiodocker.exceptions import DockerError
|
|
9
10
|
from docker import DockerClient
|
|
11
|
+
from docker.utils import parse_repository_tag
|
|
10
12
|
from manta_common.errors import MantaDockerError
|
|
11
13
|
from manta_common.traces import Tracer
|
|
12
14
|
|
|
@@ -35,6 +37,27 @@ class ImageFreshness(Enum):
|
|
|
35
37
|
UNKNOWN = "unknown"
|
|
36
38
|
|
|
37
39
|
|
|
40
|
+
# The daemon's own vocabulary for an image with no tag and no digest -- one
|
|
41
|
+
# nobody can name and no launch can reach.
|
|
42
|
+
_DANGLING = {"dangling": ["true"]}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _repository(image: str) -> str:
|
|
46
|
+
"""The repository of an image reference, without tag or digest.
|
|
47
|
+
|
|
48
|
+
docker-py's parser, because a registry port (``localhost:5000/r/i:t``) is
|
|
49
|
+
not a tag separator and a hand-written split gets it wrong.
|
|
50
|
+
"""
|
|
51
|
+
repository, _tag = parse_repository_tag(image)
|
|
52
|
+
return repository
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _human(size: int) -> str:
|
|
56
|
+
if size >= 2**30:
|
|
57
|
+
return f"{size / 2**30:.1f} GiB"
|
|
58
|
+
return f"{size / 2**20:.1f} MiB"
|
|
59
|
+
|
|
60
|
+
|
|
38
61
|
class DockerRaiser:
|
|
39
62
|
async_client: Docker
|
|
40
63
|
tracer: Tracer
|
|
@@ -45,6 +68,8 @@ class DockerRaiser:
|
|
|
45
68
|
_PULL_EXECUTOR = concurrent.futures.ThreadPoolExecutor(
|
|
46
69
|
max_workers=4, thread_name_prefix="docker-pull"
|
|
47
70
|
)
|
|
71
|
+
# Per instance, created on first use: this class has no __init__ of its own.
|
|
72
|
+
_pull_lock: asyncio.Lock | None = None
|
|
48
73
|
|
|
49
74
|
async def image_freshness(self, image: str) -> ImageFreshness:
|
|
50
75
|
"""
|
|
@@ -149,6 +174,39 @@ class DockerRaiser:
|
|
|
149
174
|
local-only image with nothing to compare), ``UNKNOWN`` when the
|
|
150
175
|
registry could not be asked. ``STALE`` is never returned — a stale
|
|
151
176
|
image is pulled, and a pull that fails raises instead of returning.
|
|
177
|
+
|
|
178
|
+
Raises
|
|
179
|
+
------
|
|
180
|
+
MantaDockerError
|
|
181
|
+
The pull failed, or was refused because the daemon's filesystem
|
|
182
|
+
cannot hold the image (below).
|
|
183
|
+
|
|
184
|
+
Notes
|
|
185
|
+
-----
|
|
186
|
+
A pull is one serialized sequence, ``_pull_replacing``: prune, measure,
|
|
187
|
+
refuse, pull, remove. The ORDER is the invariant — a moved tag must never
|
|
188
|
+
need more free space than a single image. Removing the image a tag left
|
|
189
|
+
behind AFTER the pull cannot be what makes room, because on a node
|
|
190
|
+
holding N leftovers the pull is the thing that fails; so every dangling
|
|
191
|
+
image no container uses goes BEFORE the pull, the free space on the
|
|
192
|
+
daemon's data root is measured AFTER they are gone, and a pull that still
|
|
193
|
+
cannot fit is refused in one readable error rather than dying inside
|
|
194
|
+
docker-py with its reason swallowed. One lock covers the whole sequence:
|
|
195
|
+
two concurrent starts measuring the same free space would each admit a
|
|
196
|
+
pull the other one spends.
|
|
197
|
+
|
|
198
|
+
Two facts of the containerd image store shape those steps. An image a
|
|
199
|
+
tag moved off keeps NO RepoTags and NO RepoDigests, so the leftovers of
|
|
200
|
+
a repository cannot be asked for by name — ``dangling`` is the criterion,
|
|
201
|
+
narrowed to images no container (running or exited) uses, unforced so the
|
|
202
|
+
daemon's own 409 is the backstop. And ``GET /images/json`` reports
|
|
203
|
+
``Size`` as the on-disk footprint while ``inspect`` reports the
|
|
204
|
+
compressed content alone, so the pull is sized from the listing: the
|
|
205
|
+
largest local image of the same repository, a floor rather than the
|
|
206
|
+
figure (layer sharing lowers the need, download scratch space raises
|
|
207
|
+
it). Housekeeping never fails a launch, and a question the daemon cannot
|
|
208
|
+
answer — no local image of the repository, a data root not on this host
|
|
209
|
+
— proceeds and says so, the rule ``UNKNOWN`` already sets above.
|
|
152
210
|
"""
|
|
153
211
|
if force_pull:
|
|
154
212
|
# Ahead of the comparison, not merged into it: the comparison is a
|
|
@@ -172,10 +230,97 @@ class DockerRaiser:
|
|
|
172
230
|
return freshness
|
|
173
231
|
|
|
174
232
|
async def _pull_replacing(self, image: str) -> None:
|
|
175
|
-
"""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
233
|
+
"""Prune, measure, refuse, pull, remove what the tag left — under one lock."""
|
|
234
|
+
if self._pull_lock is None:
|
|
235
|
+
self._pull_lock = asyncio.Lock()
|
|
236
|
+
async with self._pull_lock:
|
|
237
|
+
previous_id = await self._local_image_id(image)
|
|
238
|
+
await self._prune_dangling_images(image)
|
|
239
|
+
await self._refuse_pull_without_room(image)
|
|
240
|
+
await self.pull_image_or_raise(image)
|
|
241
|
+
await self._remove_superseded_image(image, previous_id)
|
|
242
|
+
|
|
243
|
+
async def _prune_dangling_images(self, image: str) -> None:
|
|
244
|
+
"""Delete every dangling image no container uses; unforced, never raises."""
|
|
245
|
+
try:
|
|
246
|
+
dangling = await self.async_client.images.list(filters=_DANGLING)
|
|
247
|
+
containers = await self.async_client.containers.list(all=True)
|
|
248
|
+
except Exception as exc:
|
|
249
|
+
self.tracer.warning(
|
|
250
|
+
f"Dangling images not pruned before pulling {image}: {exc!r}"
|
|
251
|
+
)
|
|
252
|
+
return
|
|
253
|
+
in_use = {container["ImageID"] for container in containers}
|
|
254
|
+
for entry in dangling:
|
|
255
|
+
image_id = entry.get("Id")
|
|
256
|
+
if not image_id:
|
|
257
|
+
continue
|
|
258
|
+
if image_id in in_use:
|
|
259
|
+
self.tracer.info(
|
|
260
|
+
f"Dangling image {image_id} kept before pulling {image}: a "
|
|
261
|
+
f"container still uses it"
|
|
262
|
+
)
|
|
263
|
+
continue
|
|
264
|
+
try:
|
|
265
|
+
await self.async_client.images.delete(image_id)
|
|
266
|
+
except Exception as exc:
|
|
267
|
+
self.tracer.warning(
|
|
268
|
+
f"Dangling image {image_id} kept before pulling {image}: {exc!r}"
|
|
269
|
+
)
|
|
270
|
+
continue
|
|
271
|
+
self.tracer.info(
|
|
272
|
+
f"Removed dangling image {image_id} "
|
|
273
|
+
f"({_human(int(entry.get('Size') or 0))}) before pulling {image}"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
async def _refuse_pull_without_room(self, image: str) -> None:
|
|
277
|
+
"""Raise before the pull when the daemon's filesystem cannot hold it."""
|
|
278
|
+
try:
|
|
279
|
+
info = await asyncio.to_thread(self.docker_client.info)
|
|
280
|
+
root = info["DockerRootDir"]
|
|
281
|
+
except Exception as exc:
|
|
282
|
+
self.tracer.warning(
|
|
283
|
+
f"Free space not checked before pulling {image}: the daemon's data "
|
|
284
|
+
f"root could not be read: {exc!r}"
|
|
285
|
+
)
|
|
286
|
+
return
|
|
287
|
+
try:
|
|
288
|
+
free = (await asyncio.to_thread(shutil.disk_usage, root)).free
|
|
289
|
+
except OSError as exc:
|
|
290
|
+
# A remote daemon's root is not on this filesystem.
|
|
291
|
+
self.tracer.warning(
|
|
292
|
+
f"Free space not checked before pulling {image}: {root} could not "
|
|
293
|
+
f"be measured from this host: {exc!r}"
|
|
294
|
+
)
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
repository = _repository(image)
|
|
298
|
+
try:
|
|
299
|
+
siblings = await self.async_client.images.list(
|
|
300
|
+
filters={"reference": [repository]}
|
|
301
|
+
)
|
|
302
|
+
except Exception as exc:
|
|
303
|
+
self.tracer.warning(
|
|
304
|
+
f"Free space not checked before pulling {image}: the local images "
|
|
305
|
+
f"of {repository} could not be listed: {exc!r}"
|
|
306
|
+
)
|
|
307
|
+
return
|
|
308
|
+
needed = max((int(entry.get("Size") or 0) for entry in siblings), default=0)
|
|
309
|
+
if needed == 0:
|
|
310
|
+
self.tracer.info(
|
|
311
|
+
f"{_human(free)} free under {root}; no local image of {repository} "
|
|
312
|
+
f"to size the pull of {image} against"
|
|
313
|
+
)
|
|
314
|
+
return
|
|
315
|
+
if free < needed:
|
|
316
|
+
message = (
|
|
317
|
+
f"Image {image} not pulled: {_human(free)} free under {root}, and "
|
|
318
|
+
f"the {repository} image already on this node occupies "
|
|
319
|
+
f"{_human(needed)}. A pull that size cannot complete here; the "
|
|
320
|
+
f"node already pruned what it could -- free the disk and retry."
|
|
321
|
+
)
|
|
322
|
+
self.tracer.error(message)
|
|
323
|
+
raise MantaDockerError(message, metadata=self.tracer.metadata)
|
|
179
324
|
|
|
180
325
|
async def _local_image_id(self, image: str) -> str | None:
|
|
181
326
|
"""The image id ``image`` resolves to on this daemon, or None if absent."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: manta_node
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6b5.dev672
|
|
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 <hugo.miralles@manta-tech.io>, Matthew Thompson <matthew.thompson@manta-tech.io>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -28,7 +28,7 @@ Requires-Dist: pyyaml
|
|
|
28
28
|
Requires-Dist: psutil
|
|
29
29
|
Requires-Dist: msgpack
|
|
30
30
|
Requires-Dist: aiomqtt<3.0,>=2.5.1
|
|
31
|
-
Requires-Dist: manta-common-core<=0.
|
|
31
|
+
Requires-Dist: manta-common-core<=0.6b5,>=0.6b5.dev0
|
|
32
32
|
Requires-Dist: toml
|
|
33
33
|
Requires-Dist: tomli; python_version < "3.11"
|
|
34
34
|
Requires-Dist: tomli-w
|
|
@@ -51,7 +51,7 @@ Dynamic: license-file
|
|
|
51
51
|
|
|
52
52
|
# Manta Node Python
|
|
53
53
|
|
|
54
|
-

|
|
55
55
|

|
|
56
56
|

|
|
57
57
|

|
|
@@ -5,10 +5,10 @@ manta_node/task_manager.py,sha256=xRE-yKdUW0FFR8vtX1jDwl1AxTbqVD_JS7QVR62GA00,38
|
|
|
5
5
|
manta_node/tasks.py,sha256=o837Hi-KFVJg9LF5iOtE8oD6UuTOfrEPWR-JOdhRGeY,6983
|
|
6
6
|
manta_node/utils.py,sha256=Hw4Iw2jYQYsfVLVJuBMt-_rMFBeml3nCDE2jiiXc8YM,1155
|
|
7
7
|
manta_node/cli/__init__.py,sha256=arzO3mXDJ-E2yeeytbZfN7mCswwxdQ43_QjyeL2E5yE,134
|
|
8
|
-
manta_node/cli/_version_generated.py,sha256=
|
|
8
|
+
manta_node/cli/_version_generated.py,sha256=kQBkXymO8TdGeYtp5QcYpA4uOGjqOWyB_bV_1WRaGGk,275
|
|
9
9
|
manta_node/cli/config_manager.py,sha256=BCju9zqzraviL3KrzRnB-vZQUY__Q5kuEqb7EdB8YdI,4409
|
|
10
10
|
manta_node/cli/main.py,sha256=dZa6zrypWBvpN8hd4qpxevvlyNFhxKINCg1537NJ36w,4318
|
|
11
|
-
manta_node/cli/version.py,sha256=
|
|
11
|
+
manta_node/cli/version.py,sha256=3lrnIWpOosszBN-VsCBVLxDgpvCC7IB0aDIKx_3e7hI,3115
|
|
12
12
|
manta_node/cli/commands/__init__.py,sha256=GKLOURXZqec5B3H3EiwX41xDISGg6cO_kCueoyZvt0Q,505
|
|
13
13
|
manta_node/cli/commands/cluster.py,sha256=kUVopCN4xkEk-IR1aKpr7bfBSQEtG6pEj_hlWno5SZQ,14257
|
|
14
14
|
manta_node/cli/commands/config.py,sha256=BiarCD0S8rcNYbio4RLzDvUS7A50s5dd5F4TiZqLR6M,16577
|
|
@@ -26,7 +26,7 @@ manta_node/infrastructure/executor.py,sha256=wbH5-G4Y3iNTPr8ROWRDT-bWD-CYl1YFlZQ
|
|
|
26
26
|
manta_node/infrastructure/config/__init__.py,sha256=VnKidPAVfzlZMb5WM-MggtsDLM8tdAFFWlB7WSiawzc,617
|
|
27
27
|
manta_node/infrastructure/config/node_config_manager.py,sha256=Js6PISjR_JRXMhgFC7lzI6StjW5paFhdhmtteao9j5A,35228
|
|
28
28
|
manta_node/infrastructure/container/__init__.py,sha256=ACDyzGvtM7nfTsBXDAYFjkKgU2yu4Umj3CQ_rlRfw1I,300
|
|
29
|
-
manta_node/infrastructure/container/docker_adapter.py,sha256=
|
|
29
|
+
manta_node/infrastructure/container/docker_adapter.py,sha256=hI5YenhcZwn19_chA8UM2RT6cmcnknzVcmu7AeHrkWo,24221
|
|
30
30
|
manta_node/infrastructure/container/executor.py,sha256=0aTFXDv-oXz81qwGi6zl_4qtkFriPWzn7iiiTRg7KJM,7274
|
|
31
31
|
manta_node/infrastructure/container/image_executor.py,sha256=noNwde03hw9k3EnK1JuQGtByUf-XDHqx623X0DKgHZ0,10315
|
|
32
32
|
manta_node/infrastructure/container/manager.py,sha256=kylDl-5VZk01tzSXVrWI9VPn8mqi6tV-7Rf1tkMgee0,15790
|
|
@@ -51,9 +51,9 @@ manta_node/infrastructure/process/executor.py,sha256=Ab1XvNuelEGCD5KdZLgYYnpjYZ5
|
|
|
51
51
|
manta_node/infrastructure/security/__init__.py,sha256=lI6vkbUmfmt0m363iO4HzuzVNoQAc7Clp2R5vAhZbgE,64
|
|
52
52
|
manta_node/infrastructure/security/auth_agent.py,sha256=WkPTg_g0gix7xomb_on_Ft3TNX0FpeqS7gwgJW9mTvk,11037
|
|
53
53
|
manta_node/infrastructure/security/token_provider.py,sha256=u3bcbQYNBskFzunO-wOSZpGkDN_CU1BCB5HUbZTC4Vg,1832
|
|
54
|
-
manta_node-0.
|
|
55
|
-
manta_node-0.
|
|
56
|
-
manta_node-0.
|
|
57
|
-
manta_node-0.
|
|
58
|
-
manta_node-0.
|
|
59
|
-
manta_node-0.
|
|
54
|
+
manta_node-0.6b5.dev672.dist-info/licenses/LICENSE,sha256=9mBgLhiB9goQPQjg9XQJ6FjgVxYw9Q8lyOZYkPGwF_c,34484
|
|
55
|
+
manta_node-0.6b5.dev672.dist-info/METADATA,sha256=F8qtn5_RUOnONURnsYxEbm2SWM41xJQ04AbK7eAc8z0,4213
|
|
56
|
+
manta_node-0.6b5.dev672.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
57
|
+
manta_node-0.6b5.dev672.dist-info/entry_points.txt,sha256=C_zcw6mdmztNxVUZGLZ83UH5TwcSuqb2C-rB4e2CQoc,56
|
|
58
|
+
manta_node-0.6b5.dev672.dist-info/top_level.txt,sha256=nbT5J5WETuXM9UPoQckl29uUOq8wMYdRb9QDx5kAFeo,11
|
|
59
|
+
manta_node-0.6b5.dev672.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|